ha:6:{s:4:"name";s:10:"GenisysPro";s:7:"version";s:6:"1.1dev";s:3:"api";s:5:"3.0.1";s:9:"minecraft";s:48:"v1.1.0,v1.1.1,v1.1.2,v1.1.3,v1.1.4,v1.1.5,v1.1.7";s:8:"protocol";i:113;s:12:"creationDate";i:1507334400;}.git/refs/heads/master)_Y)U .git/HEAD)_Y)Usrc/pocketmine/Achievement.php_Y{src/pocketmine/CrashDump.phpn"_Yn":src/pocketmine/IPlayer.phpv_Yv src/pocketmine/MemoryManager.php4_Y4a= src/pocketmine/OfflinePlayer.php_Y<ƶsrc/pocketmine/Player.php_YiENsrc/pocketmine/PocketMine.phpg=_Yg=ѣsrc/pocketmine/Server.phpp=_Yp=[src/pocketmine/Thread.php _Y  src/pocketmine/ThreadManager.php_Ynksrc/pocketmine/Worker.phpK _YK R4"#src/pocketmine/block/AcaciaDoor.php_YmP)src/pocketmine/block/AcaciaWoodStairs.php_YRh&src/pocketmine/block/ActivatorRail.php_Yp+src/pocketmine/block/ActiveRedstoneLamp.php _Y >src/pocketmine/block/Air.php _Y -src/pocketmine/block/Anvil.php _Y Zƶsrc/pocketmine/block/Beacon.php _Y isrc/pocketmine/block/Bed.php_Yi^ src/pocketmine/block/Bedrock.php_YD !src/pocketmine/block/Beetroot.phph_YhB "src/pocketmine/block/BirchDoor.php_Y6UD(src/pocketmine/block/BirchWoodStairs.php_Yֆp.src/pocketmine/block/BlackGlazedTerracotta.phpu_Yusrc/pocketmine/block/Block.php~f_Y~f !src/pocketmine/block/BlockIds.php#_Y#&-src/pocketmine/block/BlueGlazedTerracotta.phpp_Yp`¶"src/pocketmine/block/Bookshelf.php_Yݶ%src/pocketmine/block/BrewingStand.php2_Y2|N-$src/pocketmine/block/BrickStairs.php"_Y"KFsrc/pocketmine/block/Bricks.php_Y܌]=.src/pocketmine/block/BrownGlazedTerracotta.phpt_Yt@<3&src/pocketmine/block/BrownMushroom.phpR_YR8e+src/pocketmine/block/BrownMushroomBlock.php_YW'src/pocketmine/block/BurningFurnace.phpR_YR>Fȶsrc/pocketmine/block/Cactus.phpj_Yjsrc/pocketmine/block/Cake.php_Y꾶src/pocketmine/block/Carpet.php _Y B,Frsrc/pocketmine/block/Carrot.php_Y0nG!src/pocketmine/block/Cauldron.php+_Y+´Gsrc/pocketmine/block/Chest.php_YDz%src/pocketmine/block/ChorusFlower.php_Y٢S$src/pocketmine/block/ChorusPlant.php _Y Xisrc/pocketmine/block/Clay.phpU_YUl=ȶsrc/pocketmine/block/Coal.php:_Y:SCC src/pocketmine/block/CoalOre.php_Y=H$src/pocketmine/block/Cobblestone.php_Yy*src/pocketmine/block/CobblestoneStairs.php_Y-src/pocketmine/block/Cobweb.php_Y⯶#src/pocketmine/block/CocoaBlock.php_Ydö%src/pocketmine/block/CommandBlock.php_Y$=!src/pocketmine/block/Concrete.php/_Y/l'src/pocketmine/block/ConcretePowder.php _Y 8vƶsrc/pocketmine/block/Crops.php _Y I( -src/pocketmine/block/CyanGlazedTerracotta.phpp_Yp=ȶ"src/pocketmine/block/Dandelion.php_Y/$src/pocketmine/block/DarkOakDoor.php_YN*src/pocketmine/block/DarkOakWoodStairs.php_Y?ɶ)src/pocketmine/block/DaylightDetector.phpO _YO 9{1src/pocketmine/block/DaylightDetectorInverted.phpw_Yw"!src/pocketmine/block/DeadBush.php _Y o%src/pocketmine/block/DetectorRail.php_YZPev src/pocketmine/block/Diamond.php_Ya#src/pocketmine/block/DiamondOre.php/_Y/Nsrc/pocketmine/block/Dirt.phpj_Yj"src/pocketmine/block/Dispenser.php_Y9src/pocketmine/block/Door.php_Y> p$src/pocketmine/block/DoublePlant.php3_Y3dJ3/src/pocketmine/block/DoubleRedSandstoneSlab.php_Y, Ƕ#src/pocketmine/block/DoubleSlab.php_Y'src/pocketmine/block/DoubleWoodSlab.php]_Y]"src/pocketmine/block/DragonEgg.php_YrY src/pocketmine/block/Dropper.php_Y_\,src/pocketmine/block/ElectricalAppliance.php_Y Ķ src/pocketmine/block/Emerald.php_YG#src/pocketmine/block/EmeraldOre.php._Y.T|(src/pocketmine/block/EnchantingTable.php(_Y(?"src/pocketmine/block/EndPortal.php_YYA'src/pocketmine/block/EndPortalFrame.php_Y5gsrc/pocketmine/block/EndRod.php_YYĶ!src/pocketmine/block/EndStone.php_YH/'src/pocketmine/block/EndStoneBricks.php6_Y6hTN#src/pocketmine/block/EnderChest.php_YFZr!src/pocketmine/block/Fallable.php _Y 4N!src/pocketmine/block/Farmland.phpj_YjC]src/pocketmine/block/Fence.php2 _Y2 PU"src/pocketmine/block/FenceGate.phps _Ys mu(src/pocketmine/block/FenceGateAcacia.php_Y'src/pocketmine/block/FenceGateBirch.php_Y?x:)src/pocketmine/block/FenceGateDarkOak.php_YM(src/pocketmine/block/FenceGateJungle.php_Yf(src/pocketmine/block/FenceGateSpruce.php_YhTsrc/pocketmine/block/Fire.php_Yb!src/pocketmine/block/Flowable.php_Ysrc/pocketmine/block/Flower.php _Y #1?"src/pocketmine/block/FlowerPot.php&_Y&: * src/pocketmine/block/Furnace.php_Y̐5.src/pocketmine/block/Glass.phpw_Yw! "src/pocketmine/block/GlassPane.php_Yf#(src/pocketmine/block/GlowingObsidian.php_Y&Ġ+src/pocketmine/block/GlowingRedstoneOre.php_Y򿥶"src/pocketmine/block/Glowstone.php_YQtosrc/pocketmine/block/Gold.php_Y'() src/pocketmine/block/GoldOre.php_Yssrc/pocketmine/block/Grass.php _Y q["src/pocketmine/block/GrassPath.php_Ysrc/pocketmine/block/Gravel.phpj_Yj*k-src/pocketmine/block/GrayGlazedTerracotta.phpp_Yp Ɏ.src/pocketmine/block/GreenGlazedTerracotta.phpt_Ytw:%src/pocketmine/block/HardenedClay.php_Y  src/pocketmine/block/HayBale.php_Yu3src/pocketmine/block/HeavyWeightedPressurePlate.php_YrÏsrc/pocketmine/block/Hopper.php_Y'src/pocketmine/block/Ice.php_Y-src/pocketmine/block/InactiveRedstoneLamp.php _Y  Y)src/pocketmine/block/InvisibleBedrock.phpB_YBbsrc/pocketmine/block/Iron.php_Y!src/pocketmine/block/IronBars.php_Y)U!src/pocketmine/block/IronDoor.php_YëS src/pocketmine/block/IronOre.php_Y%src/pocketmine/block/IronTrapdoor.php_YD崶"src/pocketmine/block/ItemFrame.php_Y#src/pocketmine/block/JungleDoor.php_Y>ݶ)src/pocketmine/block/JungleWoodStairs.php_Y Ssrc/pocketmine/block/Ladder.phpe_Yeqgsrc/pocketmine/block/Lapis.php_Y!src/pocketmine/block/LapisOre.php8_Y8h|src/pocketmine/block/Lava.php _Y src/pocketmine/block/Leaves.php_Yv src/pocketmine/block/Leaves2.php_YGsrc/pocketmine/block/Lever.php _Y zؐ2src/pocketmine/block/LightBlueGlazedTerracotta.php_Ydh3src/pocketmine/block/LightWeightedPressurePlate.php_YK-src/pocketmine/block/LimeGlazedTerracotta.phpp_Ypossrc/pocketmine/block/Liquid.phpq8_Yq8|#src/pocketmine/block/LitPumpkin.phpZ_YZ 3(src/pocketmine/block/LitRedstoneLamp.php_YLt߶0src/pocketmine/block/MagentaGlazedTerracotta.php|_Y|㰣src/pocketmine/block/Melon.php_Ya{i"src/pocketmine/block/MelonStem.php| _Y| %۶ src/pocketmine/block/MobHead.php[ _Y[ Yn'src/pocketmine/block/MonsterSpawner.php _Y X"src/pocketmine/block/MossStone.php_YH5|!src/pocketmine/block/Mycelium.php _Y 77$src/pocketmine/block/NetherBrick.php_YU)src/pocketmine/block/NetherBrickFence.php_Y;*src/pocketmine/block/NetherBrickStairs.php_Yc)(src/pocketmine/block/NetherQuartzOre.php_Yh޶&src/pocketmine/block/NetherReactor.phpu_YuUz#src/pocketmine/block/NetherWart.php _Y o Ӷ"src/pocketmine/block/Noteblock.php_Y`џ#src/pocketmine/block/Netherrack.php _Y f)i!src/pocketmine/block/Obsidian.php^_Y^D{|/src/pocketmine/block/OrangeGlazedTerracotta.phpx_Yx[P"src/pocketmine/block/PackedIce.php_Y>-src/pocketmine/block/PinkGlazedTerracotta.phpp_YpQ}/(src/pocketmine/block/Planks.php(_Y(7C⭶src/pocketmine/block/Podzol.phpW_YW ֶsrc/pocketmine/block/Portal.php_Y ,lsrc/pocketmine/block/Potato.php_Yd8$src/pocketmine/block/PoweredRail.phpt_Yt (src/pocketmine/block/PoweredRepeater.php_Y|mM&src/pocketmine/block/PressurePlate.phpL_YLcѶ#src/pocketmine/block/Prismarine.php_Yi)[ƶ src/pocketmine/block/Pumpkin.php_Y$ŴV$src/pocketmine/block/PumpkinStem.php{ _Y{ p/src/pocketmine/block/PurpleGlazedTerracotta.phpx_Yxܛasrc/pocketmine/block/Purpur.php_Y{3u%src/pocketmine/block/PurpurStairs.php*_Y*1src/pocketmine/block/Quartz.php _Y ֒%src/pocketmine/block/QuartzStairs.php_Yasrc/pocketmine/block/Rail.php_Y)P,src/pocketmine/block/RedGlazedTerracotta.phpl_Yl۶$src/pocketmine/block/RedMushroom.php_Yڞζ)src/pocketmine/block/RedMushroomBlock.php_Y\%src/pocketmine/block/RedSandstone.php_Y+R)src/pocketmine/block/RedSandstoneSlab.phpx _Yx L+src/pocketmine/block/RedSandstoneStairs.php_YD6!src/pocketmine/block/Redstone.phpj_Yjm%src/pocketmine/block/RedstoneLamp.phpA_YA $src/pocketmine/block/RedstoneOre.phpq_Yqκ'src/pocketmine/block/RedstoneSource.php _Y U0&src/pocketmine/block/RedstoneTorch.php_YiS%src/pocketmine/block/RedstoneWire.php5_Y5E src/pocketmine/block/Sand.php_Y~i"src/pocketmine/block/Sandstone.php_Y/lж(src/pocketmine/block/SandstoneStairs.php_Y1 src/pocketmine/block/Sapling.php,_Y,D$ɶ#src/pocketmine/block/SeaLantern.php_Yh#!src/pocketmine/block/SignPost.phpf _Yf +/src/pocketmine/block/SilverGlazedTerracotta.phpx_YxefV#src/pocketmine/block/SkullBlock.php"_Y"bysrc/pocketmine/block/Slab.php_YY#src/pocketmine/block/SlimeBlock.php_Y;-dsrc/pocketmine/block/Snow.phpX_YXZ"src/pocketmine/block/SnowLayer.php _Y sd src/pocketmine/block/Solid.phpW_YWZ#src/pocketmine/block/SolidLight.php_Y!src/pocketmine/block/SoulSand.php_Ysmsrc/pocketmine/block/Sponge.php _Y 1E#src/pocketmine/block/SpruceDoor.php_Y̩%)src/pocketmine/block/SpruceWoodStairs.php_Y0$src/pocketmine/block/StainedClay.phpl_Ylڛsrc/pocketmine/block/Stair.php_Y }FӶ"src/pocketmine/block/StillLava.php6_Y61#src/pocketmine/block/StillWater.php9_Y9src/pocketmine/block/Stone.php_Y7:^)src/pocketmine/block/StoneBrickStairs.php_YY5N߶$src/pocketmine/block/StoneBricks.php_Y$src/pocketmine/block/StoneButton.phpu_Yun+src/pocketmine/block/StonePressurePlate.php_Yq\e"src/pocketmine/block/StoneWall.php _Y [ڶ$src/pocketmine/block/Stonecutter.php_Yf"src/pocketmine/block/Sugarcane.phpw_YwjԶsrc/pocketmine/block/TNT.php4_Y47"src/pocketmine/block/TallGrass.php _Y ΐ6src/pocketmine/block/Thin.php_Ysrc/pocketmine/block/Torch.php _Y 9$src/pocketmine/block/Transparent.phpd_Yd;:!src/pocketmine/block/Trapdoor.php_Yߎ%src/pocketmine/block/TrappedChest.php_Y>@V!src/pocketmine/block/Tripwire.php_Yx@ٶ%src/pocketmine/block/TripwireHook.php_Y-d%src/pocketmine/block/UnknownBlock.php_Y)I*src/pocketmine/block/UnpoweredRepeater.php _Y IUTsrc/pocketmine/block/Vine.php]_Y]U!src/pocketmine/block/WallSign.php@_Y@ɶsrc/pocketmine/block/Water.php*_Y*."src/pocketmine/block/WaterLily.php _Y I["src/pocketmine/block/WetSponge.php_Yɶsrc/pocketmine/block/Wheat.php_YĶ.src/pocketmine/block/WhiteGlazedTerracotta.phpt_Yt۶src/pocketmine/block/Wood.php _Y Tj'esrc/pocketmine/block/Wood2.php&_Y&;Ja!src/pocketmine/block/WoodDoor.php_Y)!src/pocketmine/block/WoodSlab.php_Y~#src/pocketmine/block/WoodStairs.php_Y:%src/pocketmine/block/WoodenButton.php_YNP,src/pocketmine/block/WoodenPressurePlate.php_Y"src/pocketmine/block/Wool.php*_Y*:E"src/pocketmine/block/Workbench.phpY_YYxWն/src/pocketmine/block/YellowGlazedTerracotta.phpx_Yx`r "src/pocketmine/command/Command.phpn"_Yn"ٯ*src/pocketmine/command/CommandExecutor.php_Yl\%src/pocketmine/command/CommandMap.phpz_Yzݙ#(src/pocketmine/command/CommandReader.php_Y(src/pocketmine/command/CommandSender.php _Y Jd/src/pocketmine/command/ConsoleCommandSender.php _Y 6\t0src/pocketmine/command/FormattedCommandAlias.php _Y (src/pocketmine/command/PluginCommand.php_Yx)4src/pocketmine/command/PluginIdentifiableCommand.phpZ_YZ䛱R5src/pocketmine/command/RemoteConsoleCommandSender.phpy_Yy{A\+src/pocketmine/command/SimpleCommandMap.php`7_Y`7|7NT7src/pocketmine/command/defaults/BanCidByNameCommand.phpy _Yy >];1src/pocketmine/command/defaults/BanCidCommand.php7 _Y7 \D.src/pocketmine/command/defaults/BanCommand.php_YފZ6src/pocketmine/command/defaults/BanIpByNameCommand.php _Y ζ0src/pocketmine/command/defaults/BanIpCommand.phpP _YP 82src/pocketmine/command/defaults/BanListCommand.php _Y %70src/pocketmine/command/defaults/BiomeCommand.php_Y++/src/pocketmine/command/defaults/CaveCommand.php4_Y4E4src/pocketmine/command/defaults/ChunkInfoCommand.php _Y f:src/pocketmine/command/defaults/DefaultGamemodeCommand.php _Y J/src/pocketmine/command/defaults/DeopCommand.php_Yk95src/pocketmine/command/defaults/DifficultyCommand.phpD _YD '5src/pocketmine/command/defaults/DumpMemoryCommand.php_YC쪶1src/pocketmine/command/defaults/EffectCommand.php_Y_,ƶ2src/pocketmine/command/defaults/EnchantCommand.php_ _Y_ 6src/pocketmine/command/defaults/ExtractPharCommand.phpP_YP|M8src/pocketmine/command/defaults/ExtractPluginCommand.phpU_YUZP/src/pocketmine/command/defaults/FillCommand.phpT_YT,٭3src/pocketmine/command/defaults/GamemodeCommand.phpT _YT Ѡ;src/pocketmine/command/defaults/GarbageCollectorCommand.phpg _Yg &r/src/pocketmine/command/defaults/GiveCommand.php _Y /src/pocketmine/command/defaults/HelpCommand.php _Y 강/src/pocketmine/command/defaults/KickCommand.php_Yx|v/src/pocketmine/command/defaults/KillCommand.php, _Y, w/src/pocketmine/command/defaults/ListCommand.phpv_Yvz׶5src/pocketmine/command/defaults/LoadPluginCommand.php_Y$src/pocketmine/event/HandlerList.phpW_YWQE%src/pocketmine/event/LevelTimings.php#_Y#jSa!src/pocketmine/event/Listener.php_YP:D&src/pocketmine/event/TextContainer.php_YR k src/pocketmine/event/Timings.php#_Y#_T@'src/pocketmine/event/TimingsHandler.php_Y Rv--src/pocketmine/event/TranslationContainer.php_Y cZ.src/pocketmine/event/block/BlockBreakEvent.phpM _YM "-src/pocketmine/event/block/BlockBurnEvent.phpP_YPa1)src/pocketmine/event/block/BlockEvent.phpf_Yfeʮ-src/pocketmine/event/block/BlockFormEvent.php`_Y`}u-src/pocketmine/event/block/BlockGrowEvent.php_YA{o.src/pocketmine/event/block/BlockPlaceEvent.php_Y^/src/pocketmine/event/block/BlockSpreadEvent.php#_Y#]~/src/pocketmine/event/block/BlockUpdateEvent.php_YgO)5src/pocketmine/event/block/ItemFrameDropItemEvent.php_Yp/src/pocketmine/event/block/LeavesDecayEvent.php)_Y).src/pocketmine/event/block/SignChangeEvent.php_Y5A1src/pocketmine/event/entity/CreeperPowerEvent.php=_Y=2H6src/pocketmine/event/entity/EntityArmorChangeEvent.php_Y$6src/pocketmine/event/entity/EntityBlockChangeEvent.php_Y_K9src/pocketmine/event/entity/EntityCombustByBlockEvent.php_Yଦ,:src/pocketmine/event/entity/EntityCombustByEntityEvent.php_YAc2src/pocketmine/event/entity/EntityCombustEvent.php_Y3+Ӷ8src/pocketmine/event/entity/EntityDamageByBlockEvent.php_Y-~C>src/pocketmine/event/entity/EntityDamageByChildEntityEvent.phpH_YH 9src/pocketmine/event/entity/EntityDamageByEntityEvent.php_Y71src/pocketmine/event/entity/EntityDamageEvent.php"_Y"_?{0src/pocketmine/event/entity/EntityDeathEvent.php_YhK2src/pocketmine/event/entity/EntityDespawnEvent.php6_Y66src/pocketmine/event/entity/EntityDrinkPotionEvent.php_Ytq3src/pocketmine/event/entity/EntityEatBlockEvent.phpX_YXsZ.src/pocketmine/event/entity/EntityEatEvent.php _Y ~W2src/pocketmine/event/entity/EntityEatItemEvent.php_Y4src/pocketmine/event/entity/EntityEffectAddEvent.php_Y^E 7src/pocketmine/event/entity/EntityEffectRemoveEvent.php_Ye+src/pocketmine/event/entity/EntityEvent.php_YN2src/pocketmine/event/entity/EntityExplodeEvent.php_YĶ3src/pocketmine/event/entity/EntityGenerateEvent.php_Y:src/pocketmine/event/entity/EntityInventoryChangeEvent.php_Y6src/pocketmine/event/entity/EntityLevelChangeEvent.php_Yd1src/pocketmine/event/entity/EntityMotionEvent.php_Y->7src/pocketmine/event/entity/EntityRegainHealthEvent.php`_Y`t53src/pocketmine/event/entity/EntityShootBowEvent.php_Ya50src/pocketmine/event/entity/EntitySpawnEvent.php_Y'P3src/pocketmine/event/entity/EntityTeleportEvent.php_YXH3src/pocketmine/event/entity/ExplosionPrimeEvent.php_Y:}0src/pocketmine/event/entity/ItemDespawnEvent.phpR_YR5ߥ.src/pocketmine/event/entity/ItemSpawnEvent.php_Y ۇ2src/pocketmine/event/entity/ProjectileHitEvent.php9_Y9<5src/pocketmine/event/entity/ProjectileLaunchEvent.phpu_Yuu4src/pocketmine/event/inventory/AnvilProcessEvent.php<_Y<`c1src/pocketmine/event/inventory/CraftItemEvent.php_Y'C3src/pocketmine/event/inventory/FurnaceBurnEvent.php_Y #U4src/pocketmine/event/inventory/FurnaceSmeltEvent.php_Y6src/pocketmine/event/inventory/InventoryCloseEvent.php_Y+$71src/pocketmine/event/inventory/InventoryEvent.php6_Y62b5src/pocketmine/event/inventory/InventoryOpenEvent.php_Yza^<src/pocketmine/event/inventory/InventoryPickupArrowEvent.php _Y Lj;src/pocketmine/event/inventory/InventoryPickupItemEvent.php_Y؟ K<src/pocketmine/event/inventory/InventoryTransactionEvent.php_Yj)src/pocketmine/event/level/ChunkEvent.php_Y`N-src/pocketmine/event/level/ChunkLoadEvent.php_Y=1src/pocketmine/event/level/ChunkPopulateEvent.php_Y=Z/src/pocketmine/event/level/ChunkUnloadEvent.php_YeV)src/pocketmine/event/level/LevelEvent.phpv_Yvf!ն-src/pocketmine/event/level/LevelInitEvent.phph_Yhץg-src/pocketmine/event/level/LevelLoadEvent.phpd_YdqS-src/pocketmine/event/level/LevelSaveEvent.phpa_Ya/src/pocketmine/event/level/LevelUnloadEvent.php_Yc/src/pocketmine/event/level/SpawnChangeEvent.phpH_YH1src/pocketmine/event/level/WeatherChangeEvent.php_Y)=src/pocketmine/event/player/PlayerAchievementAwardedEvent.php-_Y- ->4src/pocketmine/event/player/PlayerAnimationEvent.php>_Y>N3src/pocketmine/event/player/PlayerBedEnterEvent.php_Y(3src/pocketmine/event/player/PlayerBedLeaveEvent.php_Y=6src/pocketmine/event/player/PlayerBucketEmptyEvent.php_YA1src/pocketmine/event/player/PlayerBucketEvent.php_Y 5src/pocketmine/event/player/PlayerBucketFillEvent.php_YS/src/pocketmine/event/player/PlayerChatEvent.php _Y ې<src/pocketmine/event/player/PlayerCommandPreprocessEvent.php_Y'3src/pocketmine/event/player/PlayerCreationEvent.phpZ _YZ o0src/pocketmine/event/player/PlayerDeathEvent.php _Y i̶3src/pocketmine/event/player/PlayerDropItemEvent.php_Y0f +src/pocketmine/event/player/PlayerEvent.php_Y, Q2src/pocketmine/event/player/PlayerExhaustEvent.php"_Y"'iݼ;src/pocketmine/event/player/PlayerExperienceChangeEvent.phpu_Yuq/src/pocketmine/event/player/PlayerFishEvent.php_YI9src/pocketmine/event/player/PlayerGameModeChangeEvent.phpI_YIxb56src/pocketmine/event/player/PlayerGlassBottleEvent.php_Y̩7src/pocketmine/event/player/PlayerHungerChangeEvent.php_Y`rK3src/pocketmine/event/player/PlayerInteractEvent.php _Y f6src/pocketmine/event/player/PlayerItemConsumeEvent.php_Y&3src/pocketmine/event/player/PlayerItemHeldEvent.php[_Y[\Ӷ/src/pocketmine/event/player/PlayerJoinEvent.php_Yf/src/pocketmine/event/player/PlayerKickEvent.phpo_Yo0src/pocketmine/event/player/PlayerLoginEvent.php_Y[C/src/pocketmine/event/player/PlayerMoveEvent.phpK_YK][7src/pocketmine/event/player/PlayerPickupExpOrbEvent.php_Y3src/pocketmine/event/player/PlayerPreLoginEvent.php_YTH/src/pocketmine/event/player/PlayerQuitEvent.php_Y9l¶2src/pocketmine/event/player/PlayerRespawnEvent.php_Yu[6src/pocketmine/event/player/PlayerTextPreSendEvent.php_YӞ7src/pocketmine/event/player/PlayerToggleFlightEvent.php_Y]6src/pocketmine/event/player/PlayerToggleGlideEvent.phpo_Yo6src/pocketmine/event/player/PlayerToggleSneakEvent.php _Y !q7src/pocketmine/event/player/PlayerToggleSprintEvent.php_Y ]8src/pocketmine/event/player/PlayerUseFishingRodEvent.php4_Y4ʪ6src/pocketmine/event/player/cheat/PlayerCheatEvent.php_Y究<src/pocketmine/event/player/cheat/PlayerIllegalMoveEvent.php_Y2src/pocketmine/event/plugin/PluginDisableEvent.php_YPiҶ1src/pocketmine/event/plugin/PluginEnableEvent.php_YY;c+src/pocketmine/event/plugin/PluginEvent.php_Y=Ȭ6src/pocketmine/event/server/DataPacketReceiveEvent.php_Y&3src/pocketmine/event/server/DataPacketSendEvent.php_Y1.src/pocketmine/event/server/LowMemoryEvent.php_Y#S4src/pocketmine/event/server/QueryRegenerateEvent.php_Y\'08src/pocketmine/event/server/RemoteServerCommandEvent.phpt_YtU2src/pocketmine/event/server/ServerCommandEvent.php_Y[p# +src/pocketmine/event/server/ServerEvent.php_Y"e۶+src/pocketmine/inventory/AnvilInventory.php_YKt*src/pocketmine/inventory/BaseInventory.php4_Y4wM϶,src/pocketmine/inventory/BaseTransaction.php;_Y;w#>,src/pocketmine/inventory/BeaconInventory.php_Y@,src/pocketmine/inventory/BigShapedRecipe.php_YBк/src/pocketmine/inventory/BigShapelessRecipe.php_YS-src/pocketmine/inventory/BrewingInventory.php:_Y:B*src/pocketmine/inventory/BrewingRecipe.php_Ydž+src/pocketmine/inventory/ChestInventory.php _Y W_/src/pocketmine/inventory/ContainerInventory.phpz_Yz+.src/pocketmine/inventory/CraftingInventory.php_Y.,src/pocketmine/inventory/CraftingManager.php|_Y|Kc,src/pocketmine/inventory/CustomInventory.php_YGb^/src/pocketmine/inventory/DispenserInventory.phpA_YAܝ1src/pocketmine/inventory/DoubleChestInventory.php_Y0src/pocketmine/inventory/DropItemTransaction.php_Y%l-src/pocketmine/inventory/DropperInventory.php3_Y3&h-src/pocketmine/inventory/EnchantInventory.php"src/pocketmine/item/AcaciaDoor.php+_Y+Q!src/pocketmine/item/Apple.phpu_Yu2lsrc/pocketmine/item/Armor.php? _Y? src/pocketmine/item/Arrow.php_YR#src/pocketmine/item/BakedPotato.php_YSsrc/pocketmine/item/Bed.php_YZ src/pocketmine/item/Beetroot.php_Yii%src/pocketmine/item/BeetrootSeeds.php4_Y4{#$src/pocketmine/item/BeetrootSoup.phpG_YGk<!src/pocketmine/item/BirchDoor.php&_Y&Qa#src/pocketmine/item/BlazePowder.php_Yʴ src/pocketmine/item/BlazeRod.php#_Y#src/pocketmine/item/Boat.php _Y #src/pocketmine/item/Bone.php_Y*rsrc/pocketmine/item/Book.php_YR}Tsrc/pocketmine/item/Bow.php_Yim9src/pocketmine/item/Bowl.php_YEsrc/pocketmine/item/Bread.phpx_Yx@Cz$src/pocketmine/item/BrewingStand.php5_Y5src/pocketmine/item/Brick.php_YUsrc/pocketmine/item/Bucket.php _Y Z,src/pocketmine/item/Cake.php^_Y^msrc/pocketmine/item/Camera.php_Y$src/pocketmine/item/Carrot.php_Y o src/pocketmine/item/Cauldron.phpQ_YQ`/Э"src/pocketmine/item/ChainBoots.phpY_YY'src/pocketmine/item/ChainChestplate.phps_Ys"#src/pocketmine/item/ChainHelmet.php__Y_A%src/pocketmine/item/ChainLeggings.phpg_Yg#src/pocketmine/item/ChorusFruit.phpv_Yv8/cDsrc/pocketmine/item/Clay.php_Yϫsrc/pocketmine/item/Clock.php_YRsrc/pocketmine/item/Coal.php'_Y'֖src/pocketmine/item/Compass.php_Y9%src/pocketmine/item/CookedChicken.php_Y:"src/pocketmine/item/CookedFish.php_Y%u{$src/pocketmine/item/CookedMutton.phps_YsՐ&src/pocketmine/item/CookedPorkchop.php_Y>$src/pocketmine/item/CookedRabbit.php_Yh݈src/pocketmine/item/Cookie.phpx_Yx*sJw#src/pocketmine/item/DarkOakDoor.php3_Y3Psrc/pocketmine/item/Diamond.php_Y_}"src/pocketmine/item/DiamondAxe.php_YP)L$src/pocketmine/item/DiamondBoots.php__Y_x)src/pocketmine/item/DiamondChestplate.php}_Y}A<%src/pocketmine/item/DiamondHelmet.phpe_Ye?bM"src/pocketmine/item/DiamondHoe.php2_Y2lu"'src/pocketmine/item/DiamondLeggings.phpq_Yq|A氶&src/pocketmine/item/DiamondPickaxe.php_Y %src/pocketmine/item/DiamondShovel.php_Y8*Z$src/pocketmine/item/DiamondSword.php_Y(ڶsrc/pocketmine/item/Door.php8_Y8Ո%src/pocketmine/item/DragonsBreath.php_Ysrc/pocketmine/item/Dye.php _Y o$&Tsrc/pocketmine/item/Egg.php_YCNζsrc/pocketmine/item/Elytra.php_Y%src/pocketmine/item/Emerald.php_Y1E%src/pocketmine/item/EnchantedBook.php_Ybf,src/pocketmine/item/EnchantedGoldenApple.phph_Yh ӊ(src/pocketmine/item/EnchantingBottle.php_YOI"src/pocketmine/item/EnderPearl.php-_Y-C"src/pocketmine/item/EyeOfEnder.php_Y;src/pocketmine/item/Feather.php_YŶ*src/pocketmine/item/FermentedSpiderEye.php_Y&&C"src/pocketmine/item/FireCharge.php4_Y4Gsrc/pocketmine/item/Fish.phpZ _YZ \"src/pocketmine/item/FishingRod.php_Ysrc/pocketmine/item/Flint.php_YyZ"src/pocketmine/item/FlintSteel.php_Y# *!src/pocketmine/item/FlowerPot.phpW_YWb$src/pocketmine/item/Food.php_Y}½"src/pocketmine/item/FoodSource.php_Y!src/pocketmine/item/GhastTear.php_YbAo#src/pocketmine/item/GlassBottle.php _Y ¶'src/pocketmine/item/GlisteringMelon.php_YxMy%src/pocketmine/item/GlowstoneDust.php_Y{src/pocketmine/item/GoldAxe.phpq_Yqy!src/pocketmine/item/GoldBoots.phpO_YO#-p&src/pocketmine/item/GoldChestplate.phpn_Yn6Ͷ"src/pocketmine/item/GoldHelmet.phpU_YUOsrc/pocketmine/item/GoldHoe.php#_Y#].Ķ!src/pocketmine/item/GoldIngot.php_Y/ $src/pocketmine/item/GoldLeggings.phpb_Ybn,"src/pocketmine/item/GoldNugget.php_Y#src/pocketmine/item/GoldPickaxe.php_YHlݶ"src/pocketmine/item/GoldShovel.php_Y6gڶ!src/pocketmine/item/GoldSword.php|_Y|QG#src/pocketmine/item/GoldenApple.php_Y쵶$src/pocketmine/item/GoldenCarrot.php_Y۶!src/pocketmine/item/Gunpowder.php_Yhsrc/pocketmine/item/Hopper.php_Y src/pocketmine/item/IronAxe.phpq_YqT!src/pocketmine/item/IronBoots.phpP_YP#ū&src/pocketmine/item/IronChestplate.phpn_Yn)٪ src/pocketmine/item/IronDoor.php!_Y!/"src/pocketmine/item/IronHelmet.phpV_YVVsrc/pocketmine/item/IronHoe.php#_Y#^!src/pocketmine/item/IronIngot.php_Yb!ֶ$src/pocketmine/item/IronLeggings.phpb_Ybfhc#src/pocketmine/item/IronPickaxe.php_Y85"src/pocketmine/item/IronShovel.php_Yp!src/pocketmine/item/IronSword.php{_Y{src/pocketmine/item/Item.phpJ|_YJ|۝}!src/pocketmine/item/ItemBlock.php+_Y+pn!src/pocketmine/item/ItemFrame.phpr_Yr@bIsrc/pocketmine/item/ItemIds.php%_Y% "src/pocketmine/item/ItemString.php_Yԝ"src/pocketmine/item/JungleDoor.php+_Y+-src/pocketmine/item/Leather.php_Y7k$src/pocketmine/item/LeatherBoots.php^_Y^˪"src/pocketmine/item/LeatherCap.phpX_YXw)$src/pocketmine/item/LeatherPants.phpd_Yd>$src/pocketmine/item/LeatherTunic.phph_Yh2"src/pocketmine/item/MagmaCream.php_Yosrc/pocketmine/item/Melon.phpt_Yt ""src/pocketmine/item/MelonSeeds.php$_Y$v src/pocketmine/item/Minecart.php _Y D$src/pocketmine/item/MushroomStew.php?_Y?w'ض#src/pocketmine/item/NetherBrick.php_YO"$src/pocketmine/item/NetherQuartz.php_Y "src/pocketmine/item/SpruceDoor.php+_Y+%src/pocketmine/item/Steak.phpu_Yusrc/pocketmine/item/Stick.php_Y酶 src/pocketmine/item/StoneAxe.phpv_Yv src/pocketmine/item/StoneHoe.php(_Y(X$src/pocketmine/item/StonePickaxe.php_YѶ#src/pocketmine/item/StoneShovel.php_YV"src/pocketmine/item/StoneSword.php_Yٔ޶src/pocketmine/item/Sugar.php_Y.h!src/pocketmine/item/Sugarcane.php$_Y$bsrc/pocketmine/item/Tool.phpm_Ymhsrc/pocketmine/item/Wheat.php_Y|"src/pocketmine/item/WheatSeeds.php%_Y%VU؊!src/pocketmine/item/WoodenAxe.php|_Y|2 "src/pocketmine/item/WoodenDoor.php+_Y+e_>!src/pocketmine/item/WoodenHoe.php-_Y-?p,%src/pocketmine/item/WoodenPickaxe.php_YEʶ$src/pocketmine/item/WoodenShovel.php_YA,#src/pocketmine/item/WoodenSword.php_Y/src/pocketmine/item/enchantment/Enchantment.phpMG_YMG&4src/pocketmine/item/enchantment/EnchantmentEntry.php_Y-yM9src/pocketmine/item/enchantment/EnchantmentLevelTable.php:_Y:3src/pocketmine/item/enchantment/EnchantmentList.php_Y3U src/pocketmine/lang/BaseLang.php_YvY%src/pocketmine/lang/Installer/chs.ini# _Y# | p%src/pocketmine/lang/Installer/deu.ini_Y %src/pocketmine/lang/Installer/eng.iniF _YF mǶ%src/pocketmine/lang/Installer/fra.iniN_YNA%src/pocketmine/lang/Installer/ita.ini_YȬp%src/pocketmine/lang/Installer/jpn.iniB_YB<%src/pocketmine/lang/Installer/kor.iniA_YAI,%src/pocketmine/lang/Installer/rus.ini_Y< %src/pocketmine/lang/Installer/ukr.iniK_YKy%src/pocketmine/lang/Installer/zho.ini _Y bu"src/pocketmine/lang/locale/ces.ini>_Y>n"src/pocketmine/lang/locale/chs.ini|X_Y|Xf"src/pocketmine/lang/locale/deu.inic_Ycs"src/pocketmine/lang/locale/eng.ini\_Y\pr"src/pocketmine/lang/locale/fra.ini^_Y^7t"src/pocketmine/lang/locale/ind.ini;_Y;K"src/pocketmine/lang/locale/jpn.inim_Ym*G"src/pocketmine/lang/locale/kor.inio_Y>ou{-src/pocketmine/level/format/EmptySubChunk.php_Y*wT(src/pocketmine/level/format/SubChunk.php _Y 4src/pocketmine/level/format/io/BaseLevelProvider.php_Y+N1src/pocketmine/level/format/io/ChunkException.php_Y n'3src/pocketmine/level/format/io/ChunkRequestTask.php:_Y:7E-src/pocketmine/level/format/io/ChunkUtils.php _Y ji0src/pocketmine/level/format/io/LevelProvider.php-_Y-oz% 7src/pocketmine/level/format/io/LevelProviderManager.php!_Y!+;R2src/pocketmine/level/format/io/leveldb/LevelDB.php%J_Y%JMI/src/pocketmine/level/format/io/region/Anvil.php_Y NJ}2src/pocketmine/level/format/io/region/McRegion.php8_Y8U̶1src/pocketmine/level/format/io/region/PMAnvil.php_Yg56src/pocketmine/level/format/io/region/RegionLoader.php(_Y('G'src/pocketmine/level/generator/Flat.php_Yj41src/pocketmine/level/generator/GenerationTask.php _Y e9,src/pocketmine/level/generator/Generator.php_Y#]8src/pocketmine/level/generator/GeneratorRegisterTask.php_Yc:src/pocketmine/level/generator/GeneratorUnregisterTask.php_Y)t6src/pocketmine/level/generator/LightPopulationTask.phpF_YF謾1src/pocketmine/level/generator/PopulationTask.php[_Y[r=0src/pocketmine/level/generator/VoidGenerator.php _Y .src/pocketmine/level/generator/biome/Biome.php{_Y{ !ֶ6src/pocketmine/level/generator/biome/BiomeSelector.php_Y9j.src/pocketmine/level/generator/ender/Ender.php_Yj¶9src/pocketmine/level/generator/ender/biome/EnderBiome.php_YUr=src/pocketmine/level/generator/ender/populator/EnderPilar.php_Yv1src/pocketmine/level/generator/hell/HellBiome.php_Y#^%.src/pocketmine/level/generator/hell/Nether.php_YOp.src/pocketmine/level/generator/noise/Noise.php_Y1ܶ/src/pocketmine/level/generator/noise/Perlin.php_Y3}S0src/pocketmine/level/generator/noise/Simplex.phpy:_Yy:guX0src/pocketmine/level/generator/normal/Normal.php#_Y#߽M1src/pocketmine/level/generator/normal/Normal2.php+_Y+AӶ:src/pocketmine/level/generator/normal/biome/BeachBiome.php_YuKPq;src/pocketmine/level/generator/normal/biome/DesertBiome.php/_Y/uF;src/pocketmine/level/generator/normal/biome/ForestBiome.php_Y$M;src/pocketmine/level/generator/normal/biome/GrassyBiome.phpN_YNh>src/pocketmine/level/generator/normal/biome/IcePlainsBiome.php_YIh9src/pocketmine/level/generator/normal/biome/MesaBiome.php!_Y!mD>src/pocketmine/level/generator/normal/biome/MountainsBiome.phpq_Yqlζ;src/pocketmine/level/generator/normal/biome/NormalBiome.phpS_YS':src/pocketmine/level/generator/normal/biome/OceanBiome.phpc_Ycꕶ:src/pocketmine/level/generator/normal/biome/PlainBiome.phps _Ys nͶ:src/pocketmine/level/generator/normal/biome/RiverBiome.phpc_YcYa:src/pocketmine/level/generator/normal/biome/SandyBiome.phpe _Ye t]Csrc/pocketmine/level/generator/normal/biome/SmallMountainsBiome.php_Yp:src/pocketmine/level/generator/normal/biome/SnowyBiome.phpR_YR'v :src/pocketmine/level/generator/normal/biome/SwampBiome.php_YAQ:src/pocketmine/level/generator/normal/biome/TaigaBiome.php6_Y6 N;src/pocketmine/level/generator/normal/biome/WateryBiome.php _Y  rW;4src/pocketmine/level/generator/object/AcaciaTree.php_Y01src/pocketmine/level/generator/object/BigTree.php_YjC3src/pocketmine/level/generator/object/BirchTree.php_Y<5src/pocketmine/level/generator/object/DarkOakTree.php?_Y? 4src/pocketmine/level/generator/object/JungleTree.phpS_YSڤ<3src/pocketmine/level/generator/object/NetherOre.phpi _Yi R6src/pocketmine/level/generator/object/NetherOreTop.php _Y 0r1src/pocketmine/level/generator/object/OakTree.php_YMT-src/pocketmine/level/generator/object/Ore.phps _Ys 01src/pocketmine/level/generator/object/OreType.phpR_YRQyp.src/pocketmine/level/generator/object/Pond.php_Yc:9src/pocketmine/level/generator/object/PopulatorObject.phpV_YVN4src/pocketmine/level/generator/object/SpruceTree.php _Y &3src/pocketmine/level/generator/object/TallGrass.php5_Y5a:.src/pocketmine/level/generator/object/Tree.php_YMٶ3src/pocketmine/level/generator/populator/Cactus.php _Y ~=1src/pocketmine/level/generator/populator/Cave.php*_Y*A5src/pocketmine/level/generator/populator/DeadBush.php _Y :Ѷ3src/pocketmine/level/generator/populator/Flower.php _Y PO/)8src/pocketmine/level/generator/populator/GroundCover.php _Y h7src/pocketmine/level/generator/populator/GroundFire.php _Y \ٶ4src/pocketmine/level/generator/populator/LilyPad.php _Y +OWy6src/pocketmine/level/generator/populator/Mineshaft.php_Y,"6src/pocketmine/level/generator/populator/MossStone.php _Y []<src/pocketmine/level/generator/populator/NetherGlowStone.php_Yҋz7src/pocketmine/level/generator/populator/NetherLava.phpE _YE R&h6src/pocketmine/level/generator/populator/NetherOre.php_Yw]0src/pocketmine/level/generator/populator/Ore.php_Y7]1src/pocketmine/level/generator/populator/Pond.php_Y<}j6src/pocketmine/level/generator/populator/Populator.php_Y \b6src/pocketmine/level/generator/populator/Sugarcane.php+ _Y+ `6src/pocketmine/level/generator/populator/TallGrass.php _Y M1src/pocketmine/level/generator/populator/Tree.php _Y 5src/pocketmine/level/generator/populator/WaterPit.php _Y R7src/pocketmine/level/particle/AngryVillagerParticle.php_Y9src/pocketmine/level/particle/BlockForceFieldParticle.phpS_YSɶ0src/pocketmine/level/particle/BubbleParticle.php_YKZ2src/pocketmine/level/particle/CriticalParticle.php_Y+)۶6src/pocketmine/level/particle/DestroyBlockParticle.php_Y+5Q̶.src/pocketmine/level/particle/DustParticle.php_YO>1src/pocketmine/level/particle/EnchantParticle.php_Y2z:src/pocketmine/level/particle/EnchantmentTableParticle.php_YHm5src/pocketmine/level/particle/EntityFlameParticle.php_Yn1src/pocketmine/level/particle/ExplodeParticle.php_Y'"U/src/pocketmine/level/particle/FlameParticle.php_Y36src/pocketmine/level/particle/FloatingTextParticle.phpO _YO zT1src/pocketmine/level/particle/GenericParticle.php_YW .7src/pocketmine/level/particle/HappyVillagerParticle.php_Y/src/pocketmine/level/particle/HeartParticle.php _Y E85src/pocketmine/level/particle/HugeExplodeParticle.php_Y@ܦ9src/pocketmine/level/particle/HugeExplodeSeedParticle.php_Y-src/pocketmine/level/particle/InkParticle.php_YWR8src/pocketmine/level/particle/InstantEnchantParticle.php_Yo3src/pocketmine/level/particle/ItemBreakParticle.phpX_YXO2src/pocketmine/level/particle/LavaDripParticle.php_Y}m$.src/pocketmine/level/particle/LavaParticle.php_Y2src/pocketmine/level/particle/MobSpawnParticle.php_YN׶2src/pocketmine/level/particle/MobSpellParticle.php_Y9!*src/pocketmine/level/particle/Particle.php_Yl0src/pocketmine/level/particle/PortalParticle.php_YWն4src/pocketmine/level/particle/RainSplashParticle.php_YQ>V2src/pocketmine/level/particle/RedstoneParticle.php_Y; /src/pocketmine/level/particle/SmokeParticle.php_YYkӶ/src/pocketmine/level/particle/SpellParticle.php_Y@@wZ0src/pocketmine/level/particle/SplashParticle.php_Y]^/src/pocketmine/level/particle/SporeParticle.php_Y=۶1src/pocketmine/level/particle/TerrainParticle.phpG_YGu.3src/pocketmine/level/particle/WaterDripParticle.php_Y̌V/src/pocketmine/level/particle/WaterParticle.php_Y\տ54src/pocketmine/level/particle/WhiteSmokeParticle.php_YUa-src/pocketmine/level/sound/AnvilFallSound.phpT_YTo,,src/pocketmine/level/sound/AnvilUseSound.phpQ_YQn.src/pocketmine/level/sound/BlazeShootSound.phpW_YWb.src/pocketmine/level/sound/BlockPlaceSound.php_Y ./src/pocketmine/level/sound/ButtonClickSound.php _Y  9d)src/pocketmine/level/sound/ClickSound.phpG_YG&?,src/pocketmine/level/sound/DoorBumpSound.phpQ_YQ&-src/pocketmine/level/sound/DoorCrashSound.phpT_YTw(src/pocketmine/level/sound/DoorSound.phpD_YD/y4src/pocketmine/level/sound/EndermanTeleportSound.php;_Y;I-src/pocketmine/level/sound/ExpPickupSound.php,_Y,Y3+src/pocketmine/level/sound/ExplodeSound.php/_Y/g(src/pocketmine/level/sound/FizzSound.phpD_YDDz[+src/pocketmine/level/sound/GenericSound.phpg_YgyRK.src/pocketmine/level/sound/GhastShootSound.phpW_YW0 <)src/pocketmine/level/sound/GhastSound.phpG_YG$.src/pocketmine/level/sound/GraySplashSound.php8_Y8a4src/pocketmine/level/sound/ItemFrameAddItemSound.php_YTkɶ7src/pocketmine/level/sound/ItemFrameRotateItemSound.php_Y%*src/pocketmine/level/sound/LaunchSound.phpI_YIrz-src/pocketmine/level/sound/NoteblockSound.php_Y;?'src/pocketmine/level/sound/PopSound.phpA_YA" $src/pocketmine/level/sound/Sound.php_Yt|)src/pocketmine/level/sound/SpellSound.php_Y(w2*src/pocketmine/level/sound/SplashSound.php0_Y0,src/pocketmine/level/sound/TNTPrimeSound.php5_Y5gB(src/pocketmine/level/weather/Weather.phpw_Ywu%src/pocketmine/math/AxisAlignedBB.php;%_Y;%src/pocketmine/math/Math.phpG_YG°src/pocketmine/math/Matrix.php_Y|#( src/pocketmine/math/Vector2.php>_Y>ocsrc/pocketmine/math/Vector3.php%_Y%/"src/pocketmine/math/VectorMath.php_Y0*.src/pocketmine/metadata/BlockMetadataStore.php~ _Y~ t_%/src/pocketmine/metadata/EntityMetadataStore.php_Y%$.src/pocketmine/metadata/LevelMetadataStore.php_Yζ)src/pocketmine/metadata/MetadataStore.php_YMd )src/pocketmine/metadata/MetadataValue.phpz_YzC'src/pocketmine/metadata/Metadatable.php_YM/src/pocketmine/metadata/PlayerMetadataStore.php_YH~src/pocketmine/nbt/NBT.phpM_YM -wK'src/pocketmine/nbt/tag/ByteArrayTag.php6_Y6"src/pocketmine/nbt/tag/ByteTag.php_Y할 &src/pocketmine/nbt/tag/CompoundTag.php5 _Y5 M$src/pocketmine/nbt/tag/DoubleTag.php_Yf!src/pocketmine/nbt/tag/EndTag.php_YS>!:#src/pocketmine/nbt/tag/FloatTag.php_Y&ֶ&src/pocketmine/nbt/tag/IntArrayTag.phpt_Yt>;!src/pocketmine/nbt/tag/IntTag.php_Yx"src/pocketmine/nbt/tag/ListTag.php_YBŜS"src/pocketmine/nbt/tag/LongTag.php_Ysrc/pocketmine/network/mcpe/protocol/BlockEntityDataPacket.phpI_YI9^&9src/pocketmine/network/mcpe/protocol/BlockEventPacket.php_YA7?src/pocketmine/network/mcpe/protocol/BlockPickRequestPacket.php_Y@e8src/pocketmine/network/mcpe/protocol/BossEventPacket.php_Ym5src/pocketmine/network/mcpe/protocol/CameraPacket.php__Y_5qG>src/pocketmine/network/mcpe/protocol/ChangeDimensionPacket.phpF_YF`Asrc/pocketmine/network/mcpe/protocol/ChunkRadiusUpdatedPacket.phpW_YWײFsrc/pocketmine/network/mcpe/protocol/ClientToServerHandshakePacket.phpy_YyfjEsrc/pocketmine/network/mcpe/protocol/ClientboundMapItemDataPacket.php_Y!Asrc/pocketmine/network/mcpe/protocol/CommandBlockUpdatePacket.php_Y}:src/pocketmine/network/mcpe/protocol/CommandStepPacket.php_YM*=src/pocketmine/network/mcpe/protocol/ContainerClosePacket.php_YfG<src/pocketmine/network/mcpe/protocol/ContainerOpenPacket.php_YBsrc/pocketmine/network/mcpe/protocol/ContainerSetContentPacket.phpB _YB ;&?src/pocketmine/network/mcpe/protocol/ContainerSetDataPacket.php_Y?x?src/pocketmine/network/mcpe/protocol/ContainerSetSlotPacket.phpM_YMr;src/pocketmine/network/mcpe/protocol/CraftingDataPacket.php_Y%i<src/pocketmine/network/mcpe/protocol/CraftingEventPacket.php_YK3src/pocketmine/network/mcpe/protocol/DataPacket.php_YJgd9src/pocketmine/network/mcpe/protocol/DisconnectPacket.php_YN׿7src/pocketmine/network/mcpe/protocol/DropItemPacket.php_Yz:#:src/pocketmine/network/mcpe/protocol/EntityEventPacket.php_Y:!u9src/pocketmine/network/mcpe/protocol/EntityFallPacket.php_Yg˾6src/pocketmine/network/mcpe/protocol/ExplodePacket.php_Y/w!<src/pocketmine/network/mcpe/protocol/FullChunkDataPacket.php_YE˶8src/pocketmine/network/mcpe/protocol/HurtArmorPacket.php_Yl4i7src/pocketmine/network/mcpe/protocol/InteractPacket.php_Yݶ>src/pocketmine/network/mcpe/protocol/InventoryActionPacket.php_Y|@src/pocketmine/network/mcpe/protocol/ItemFrameDropItemPacket.php_Y:ݶ9src/pocketmine/network/mcpe/protocol/LevelEventPacket.php _Y $>src/pocketmine/network/mcpe/protocol/LevelSoundEventPacket.php_Yr4src/pocketmine/network/mcpe/protocol/LoginPacket.php<_Y<dH:=src/pocketmine/network/mcpe/protocol/MapInfoRequestPacket.php$_Y$'@src/pocketmine/network/mcpe/protocol/MobArmorEquipmentPacket.php_YW8src/pocketmine/network/mcpe/protocol/MobEffectPacket.php_Y6;src/pocketmine/network/mcpe/protocol/MobEquipmentPacket.php_Y0by9src/pocketmine/network/mcpe/protocol/MoveEntityPacket.phpw_Yw9src/pocketmine/network/mcpe/protocol/MovePlayerPacket.php_Yc`8src/pocketmine/network/mcpe/protocol/PlaySoundPacket.php_Yr9src/pocketmine/network/mcpe/protocol/PlayStatusPacket.php_YJjŶ;src/pocketmine/network/mcpe/protocol/PlayerActionPacket.php_Yfoʶ:src/pocketmine/network/mcpe/protocol/PlayerInputPacket.php/_Y/}9src/pocketmine/network/mcpe/protocol/PlayerListPacket.php_Y`޶5src/pocketmine/network/mcpe/protocol/ProtocolInfo.php_YhY:src/pocketmine/network/mcpe/protocol/RemoveBlockPacket.php_Y;src/pocketmine/network/mcpe/protocol/RemoveEntityPacket.php_Yw`@src/pocketmine/network/mcpe/protocol/ReplaceItemInSlotPacket.php(_Y(*Asrc/pocketmine/network/mcpe/protocol/RequestChunkRadiusPacket.php_Y_mDsrc/pocketmine/network/mcpe/protocol/ResourcePackChunkDataPacket.php_YTGsrc/pocketmine/network/mcpe/protocol/ResourcePackChunkRequestPacket.php_Y@iIsrc/pocketmine/network/mcpe/protocol/ResourcePackClientResponsePacket.php_Y3Csrc/pocketmine/network/mcpe/protocol/ResourcePackDataInfoPacket.php_Yc@src/pocketmine/network/mcpe/protocol/ResourcePackStackPacket.phpr_Yrb@src/pocketmine/network/mcpe/protocol/ResourcePacksInfoPacket.php _Y *A6src/pocketmine/network/mcpe/protocol/RespawnPacket.php%_Y%8src/pocketmine/network/mcpe/protocol/RiderJumpPacket.php=_Y=`77Fsrc/pocketmine/network/mcpe/protocol/ServerToClientHandshakePacket.php'_Y'CLJAsrc/pocketmine/network/mcpe/protocol/SetCommandsEnabledPacket.php_YUP<src/pocketmine/network/mcpe/protocol/SetDifficultyPacket.php_Yꉶ<src/pocketmine/network/mcpe/protocol/SetEntityDataPacket.php_YX<src/pocketmine/network/mcpe/protocol/SetEntityLinkPacket.php3_Y3)F>src/pocketmine/network/mcpe/protocol/SetEntityMotionPacket.phpy_Yy38src/pocketmine/network/mcpe/protocol/SetHealthPacket.php_YV@src/pocketmine/network/mcpe/protocol/SetPlayerGameTypePacket.php_YI?src/pocketmine/network/mcpe/protocol/SetSpawnPositionPacket.php0_Y0|6src/pocketmine/network/mcpe/protocol/SetTimePacket.php_Y C7src/pocketmine/network/mcpe/protocol/SetTitlePacket.php_Y+m:src/pocketmine/network/mcpe/protocol/ShowCreditsPacket.php_YyAsrc/pocketmine/network/mcpe/protocol/SpawnExperienceOrbPacket.php2_Y208src/pocketmine/network/mcpe/protocol/StartGamePacket.php _Y "M8src/pocketmine/network/mcpe/protocol/StopSoundPacket.php_Y=src/pocketmine/network/mcpe/protocol/TakeItemEntityPacket.php_Y3-3src/pocketmine/network/mcpe/protocol/TextPacket.php _Y y7src/pocketmine/network/mcpe/protocol/TransferPacket.php_YM?src/pocketmine/network/mcpe/protocol/UpdateAttributesPacket.php"_Y"|:src/pocketmine/network/mcpe/protocol/UpdateBlockPacket.phpo_YoR:src/pocketmine/network/mcpe/protocol/UpdateTradePacket.phpO_YO6src/pocketmine/network/mcpe/protocol/UseItemPacket.php0_Y0vQBsrc/pocketmine/network/mcpe/protocol/types/InventoryNetworkIds.phpq_YqMp3src/pocketmine/network/protocol/AddEntityPacket.php_YW:src/pocketmine/network/protocol/AddHangingEntityPacket.php_Y)=%7src/pocketmine/network/protocol/AddItemEntityPacket.php_Y* xǶ1src/pocketmine/network/protocol/AddItemPacket.phpj_Yj}%5src/pocketmine/network/protocol/AddPaintingPacket.phpw_Yw ,ȶ3src/pocketmine/network/protocol/AddPlayerPacket.php_YfҶ;src/pocketmine/network/protocol/AdventureSettingsPacket.phpu _Yu f!1src/pocketmine/network/protocol/AnimatePacket.php_Yp};src/pocketmine/network/protocol/AvailableCommandsPacket.php_YԶ/src/pocketmine/network/protocol/BatchPacket.phpx_YxHͱ9src/pocketmine/network/protocol/BlockEntityDataPacket.php<_Y<4src/pocketmine/network/protocol/BlockEventPacket.php_Yq:src/pocketmine/network/protocol/BlockPickRequestPacket.php_YS3src/pocketmine/network/protocol/BossEventPacket.phpx_Yxp<src/pocketmine/network/protocol/CachedEncapsulatedPacket.php_YT0src/pocketmine/network/protocol/CameraPacket.phpR_YR̓o9src/pocketmine/network/protocol/ChangeDimensionPacket.php9_Y9B)'<src/pocketmine/network/protocol/ChunkRadiusUpdatedPacket.phpJ_YJ`>Asrc/pocketmine/network/protocol/ClientToServerHandshakePacket.phpl_Yl @src/pocketmine/network/protocol/ClientboundMapItemDataPacket.php_Y.ö<src/pocketmine/network/protocol/CommandBlockUpdatePacket.php_YZ5src/pocketmine/network/protocol/CommandStepPacket.php_Ys:$y8src/pocketmine/network/protocol/ContainerClosePacket.php_YZ7src/pocketmine/network/protocol/ContainerOpenPacket.php_Y\϶=src/pocketmine/network/protocol/ContainerSetContentPacket.php5 _Y5 :src/pocketmine/network/protocol/ContainerSetDataPacket.php_YnH:src/pocketmine/network/protocol/ContainerSetSlotPacket.php@_Y@i+X6src/pocketmine/network/protocol/CraftingDataPacket.php_Y %ֶ7src/pocketmine/network/protocol/CraftingEventPacket.php}_Y}_ .src/pocketmine/network/protocol/DataPacket.php_Y&Ҷ4src/pocketmine/network/protocol/DisconnectPacket.php_Y$2src/pocketmine/network/protocol/DropItemPacket.php_YKc5src/pocketmine/network/protocol/EntityEventPacket.php_Yq4src/pocketmine/network/protocol/EntityFallPacket.php_YQ$1src/pocketmine/network/protocol/ExplodePacket.php _Y ϰ7src/pocketmine/network/protocol/FullChunkDataPacket.php_YU3src/pocketmine/network/protocol/HurtArmorPacket.phps_Yst(src/pocketmine/network/protocol/Info.php_Y*2src/pocketmine/network/protocol/InteractPacket.phpy_YyS19src/pocketmine/network/protocol/InventoryActionPacket.php_Y&;src/pocketmine/network/protocol/ItemFrameDropItemPacket.php_YG24src/pocketmine/network/protocol/LevelEventPacket.php _Y &e9src/pocketmine/network/protocol/LevelSoundEventPacket.php_YͷV$/src/pocketmine/network/protocol/LoginPacket.phpf_Yfʶ8src/pocketmine/network/protocol/MapInfoRequestPacket.php_Y{ճ;src/pocketmine/network/protocol/MobArmorEquipmentPacket.phpz_Yzi3src/pocketmine/network/protocol/MobEffectPacket.php_Y.Cc(6src/pocketmine/network/protocol/MobEquipmentPacket.php_Yy4src/pocketmine/network/protocol/MoveEntityPacket.phpj_Yjos4src/pocketmine/network/protocol/MovePlayerPacket.php_Y3$)3src/pocketmine/network/protocol/PlaySoundPacket.php_Yqd.4src/pocketmine/network/protocol/PlayStatusPacket.php_Yih6src/pocketmine/network/protocol/PlayerActionPacket.php_Y=Ŷ5src/pocketmine/network/protocol/PlayerInputPacket.php"_Y"4src/pocketmine/network/protocol/PlayerListPacket.php_Y{ն0src/pocketmine/network/protocol/ProtocolInfo.php_YI3src/pocketmine/network/protocol/RakLibInterface.php_Y5src/pocketmine/network/protocol/RemoveBlockPacket.php_Y6src/pocketmine/network/protocol/RemoveEntityPacket.php_Yr;src/pocketmine/network/protocol/ReplaceItemInSlotPacket.php_Y~g <src/pocketmine/network/protocol/RequestChunkRadiusPacket.php_Y,Q?src/pocketmine/network/protocol/ResourcePackChunkDataPacket.php_Y&Bsrc/pocketmine/network/protocol/ResourcePackChunkRequestPacket.php_YI]!Dsrc/pocketmine/network/protocol/ResourcePackClientResponsePacket.php_Ys>src/pocketmine/network/protocol/ResourcePackDataInfoPacket.php_Yy;src/pocketmine/network/protocol/ResourcePackStackPacket.phpe_Ye ?;src/pocketmine/network/protocol/ResourcePacksInfoPacket.php_Y;d1src/pocketmine/network/protocol/RespawnPacket.php_Yz%3src/pocketmine/network/protocol/RiderJumpPacket.php0_Y0\Asrc/pocketmine/network/protocol/ServerToClientHandshakePacket.php_Y=9<src/pocketmine/network/protocol/SetCommandsEnabledPacket.php_YIO7src/pocketmine/network/protocol/SetDifficultyPacket.php_YdJ)+7src/pocketmine/network/protocol/SetEntityDataPacket.php_YFS7src/pocketmine/network/protocol/SetEntityLinkPacket.php&_Y&ʗ9src/pocketmine/network/protocol/SetEntityMotionPacket.phpl_Yl3src/pocketmine/network/protocol/SetHealthPacket.php_Y W;src/pocketmine/network/protocol/SetPlayerGameTypePacket.php_Y쳽^:src/pocketmine/network/protocol/SetSpawnPositionPacket.php#_Y#1src/pocketmine/network/protocol/SetTimePacket.php_Y_P2src/pocketmine/network/protocol/SetTitlePacket.php_Yw5src/pocketmine/network/protocol/ShowCreditsPacket.php_Y+<src/pocketmine/network/protocol/SpawnExperienceOrbPacket.php%_Y%$*3src/pocketmine/network/protocol/StartGamePacket.php _Y &3src/pocketmine/network/protocol/StopSoundPacket.php_Yi]8src/pocketmine/network/protocol/TakeItemEntityPacket.php_Y9Y.src/pocketmine/network/protocol/TextPacket.php _Y i-=2src/pocketmine/network/protocol/TransferPacket.phpr_Yr:gӶ:src/pocketmine/network/protocol/UpdateAttributesPacket.php_YH<5src/pocketmine/network/protocol/UpdateBlockPacket.phpb_Yb;_ܶ5src/pocketmine/network/protocol/UpdateTradePacket.phpB_YB'r1src/pocketmine/network/protocol/UseItemPacket.php#_Y#勶=src/pocketmine/network/protocol/types/InventoryNetworkIds.phpl_YlU-src/pocketmine/network/query/QueryHandler.php_YCe$src/pocketmine/network/rcon/RCON.php_Y4r,src/pocketmine/network/rcon/RCONInstance.php(_Y(5Q$src/pocketmine/network/upnp/UPnP.php_YaBa&src/pocketmine/permission/BanEntry.phpW_YWÂW%src/pocketmine/permission/BanList.php+_Y+0src/pocketmine/permission/DefaultPermissions.phpt2_Yt2X)src/pocketmine/permission/Permissible.php_Y7-src/pocketmine/permission/PermissibleBase.phps_Ysl(src/pocketmine/permission/Permission.php|_Y|d׶2src/pocketmine/permission/PermissionAttachment.phpU _YU 혶6src/pocketmine/permission/PermissionAttachmentInfo.phpi_YiJRʶ7src/pocketmine/permission/PermissionRemovedExecutor.php_YUS,src/pocketmine/permission/ServerOperator.php_Yo4'src/pocketmine/plugin/EventExecutor.php_Y?,src/pocketmine/plugin/FolderPluginLoader.php&_Y&L-src/pocketmine/plugin/MethodEventExecutor.php_YK޶*src/pocketmine/plugin/PharPluginLoader.php_Y,kP src/pocketmine/plugin/Plugin.php _Y g$src/pocketmine/plugin/PluginBase.php _Y cڶ+src/pocketmine/plugin/PluginDescription.php5_Y5%0)src/pocketmine/plugin/PluginException.php;_Y;"")src/pocketmine/plugin/PluginLoadOrder.php_YDŽԶ&src/pocketmine/plugin/PluginLoader.php_Yl+)&src/pocketmine/plugin/PluginLogger.php/ _Y/ v~'src/pocketmine/plugin/PluginManager.php]_Y]8,src/pocketmine/plugin/RegisteredListener.php _Y 7 Ѷ,src/pocketmine/plugin/ScriptPluginLoader.phpM_YM`ʶ-src/pocketmine/resourcepacks/ResourcePack.php_YY1΁6src/pocketmine/resourcepacks/ResourcePackInfoEntry.phpI_YI4src/pocketmine/resourcepacks/ResourcePackManager.php_Y^X_3src/pocketmine/resourcepacks/ZippedResourcePack.php_YA{-src/pocketmine/resources/command_default.jsonC_YCL5+src/pocketmine/resources/creativeitems.jsonj_Yjƪw(src/pocketmine/resources/genisys_chs.yml7_Y7 6(src/pocketmine/resources/genisys_eng.yml_YAж(src/pocketmine/resources/genisys_fra.yml{_Y{Lkg(src/pocketmine/resources/genisys_jpn.ymle_YeJd(src/pocketmine/resources/genisys_rus.yml5_Y5v(src/pocketmine/resources/genisys_zho.ymlD_YD src/pocketmine/resources/map.png;_Y;V+src/pocketmine/resources/pocketmine_chs.yml_YavT+src/pocketmine/resources/pocketmine_eng.yml_Y9+src/pocketmine/resources/pocketmine_jpn.ymlV_YV +src/pocketmine/resources/pocketmine_zho.yml_YiP4%src/pocketmine/resources/recipes.jsonS_YS$l_/src/pocketmine/resources/resource_packs_chs.ymlx_Yxdjm/src/pocketmine/resources/resource_packs_eng.yml_Y/src/pocketmine/resources/resource_packs_zho.ymlx_YxTW&src/pocketmine/scheduler/AsyncPool.php_Yj&src/pocketmine/scheduler/AsyncTask.php{_Y{eh/(src/pocketmine/scheduler/AsyncWorker.php_Yz`J)src/pocketmine/scheduler/CallbackTask.php_Y0wC(src/pocketmine/scheduler/DServerTask.php_Y%'*src/pocketmine/scheduler/FileWriteTask.php_Yd2src/pocketmine/scheduler/GarbageCollectionTask.php[_Y[iJ]v'src/pocketmine/scheduler/PluginTask.php|_Y|V]>*src/pocketmine/scheduler/SendUsageTask.php_Y,src/pocketmine/scheduler/ServerScheduler.php_YH!src/pocketmine/scheduler/Task.php_Y(src/pocketmine/scheduler/TaskHandler.php _Y uʡsrc/pocketmine/tile/Beacon.phpM_YMcƶsrc/pocketmine/tile/Bed.php_Y[$src/pocketmine/tile/BrewingStand.phps_YsӶ src/pocketmine/tile/Cauldron.phpF_YFGFsrc/pocketmine/tile/Chest.phpr_Yr:p!src/pocketmine/tile/Container.php_Yx"src/pocketmine/tile/DLDetector.php^_Y^g)!src/pocketmine/tile/Dispenser.php_Ysrc/pocketmine/tile/Dropper.php_Y#$src/pocketmine/tile/EnchantTable.php_Ys2RԶ"src/pocketmine/tile/EnderChest.php_YywH!src/pocketmine/tile/FlowerPot.php _Y >tsrc/pocketmine/tile/Furnace.php$_Y$|Rpsrc/pocketmine/tile/Hopper.phpG _YG |!src/pocketmine/tile/ItemFrame.php _Y +)U"src/pocketmine/tile/MobSpawner.php_YR src/pocketmine/tile/Nameable.php_Y涽src/pocketmine/tile/Sign.php_YWrksrc/pocketmine/tile/Skull.php_YeI!src/pocketmine/tile/Spawnable.php _Y ]nsrc/pocketmine/tile/Tile.phpr_Yr!src/pocketmine/utils/Binary.php%_Y%W^߶%src/pocketmine/utils/BinaryStream.php_Y/*&src/pocketmine/utils/BlockIterator.php(_Y(_(~src/pocketmine/utils/Color.php_YG(Wsrc/pocketmine/utils/Config.php(_Y(]#src/pocketmine/utils/MainLogger.php$_Y$0$src/pocketmine/utils/MonkeyPatch.phpA_YAo"src/pocketmine/utils/Patchable.php_Yג*src/pocketmine/utils/Random.php _Y Bsrc/pocketmine/utils/Range.phpF_YFK-src/pocketmine/utils/ReversePriorityQueue.php_Y%(src/pocketmine/utils/ServerException.php_Yw0%src/pocketmine/utils/ServerKiller.php$_Y$ăZ!src/pocketmine/utils/Terminal.phpp_YpGВ#src/pocketmine/utils/TextFormat.php/_Y/(dsrc/pocketmine/utils/UUID.php_Y0src/pocketmine/utils/Utils.php-_Y-"Ǽ['src/pocketmine/utils/VectorIterator.php1)_Y1)J&src/pocketmine/utils/VersionString.php- _Y- Z#src/pocketmine/wizard/Installer.phpu"_Yu"6ف'src/pocketmine/wizard/InstallerLang.php _Y ]̕.src/raklib/Binary.php_Y:/C̶src/raklib/RakLib.phps _Ys *src/raklib/protocol/ACK.php_Y(src/raklib/protocol/ADVERTISE_SYSTEM.php_Y߫p)src/raklib/protocol/AcknowledgePacket.php_Y䣶1src/raklib/protocol/CLIENT_CONNECT_DataPacket.php_Y4src/raklib/protocol/CLIENT_DISCONNECT_DataPacket.php_Y=3src/raklib/protocol/CLIENT_HANDSHAKE_DataPacket.php_YJs%src/raklib/protocol/DATA_PACKET_0.php_YI%src/raklib/protocol/DATA_PACKET_1.php_Y`I%src/raklib/protocol/DATA_PACKET_2.php_Ya޹%src/raklib/protocol/DATA_PACKET_3.php_YS%src/raklib/protocol/DATA_PACKET_4.php_YS ն%src/raklib/protocol/DATA_PACKET_5.php_Yv%src/raklib/protocol/DATA_PACKET_6.php_Y{%src/raklib/protocol/DATA_PACKET_7.php_Y%src/raklib/protocol/DATA_PACKET_8.php_Y%%src/raklib/protocol/DATA_PACKET_9.php_Yv~$%src/raklib/protocol/DATA_PACKET_A.php_Y3J%src/raklib/protocol/DATA_PACKET_B.php_Y$%src/raklib/protocol/DATA_PACKET_C.php_YPö%src/raklib/protocol/DATA_PACKET_D.php_Y\t%src/raklib/protocol/DATA_PACKET_E.php_YH%src/raklib/protocol/DATA_PACKET_F.php_YtJ "src/raklib/protocol/DataPacket.php_Y @*src/raklib/protocol/EncapsulatedPacket.php_YL<֝src/raklib/protocol/NACK.php_Y/src/raklib/protocol/OPEN_CONNECTION_REPLY_1.php_YT[/src/raklib/protocol/OPEN_CONNECTION_REPLY_2.phpW_YWZ@1src/raklib/protocol/OPEN_CONNECTION_REQUEST_1.php_Yj01src/raklib/protocol/OPEN_CONNECTION_REQUEST_2.php_YLѶ'src/raklib/protocol/PING_DataPacket.php_YOض'src/raklib/protocol/PONG_DataPacket.php_Y`!src/raklib/protocol/Packet.php _Y H6V)src/raklib/protocol/PacketReliability.php=_Y=3src/raklib/protocol/SERVER_HANDSHAKE_DataPacket.php6_Y68x(src/raklib/protocol/UNCONNECTED_PING.php_Yn9src/raklib/protocol/UNCONNECTED_PING_OPEN_CONNECTIONS.php_Y (src/raklib/protocol/UNCONNECTED_PONG.php_YH "src/raklib/server/RakLibServer.php_Yg#src/raklib/server/ServerHandler.php_Y3"$src/raklib/server/ServerInstance.phpu_Yu3src/raklib/server/Session.phpu<_Yu<Z$src/raklib/server/SessionManager.phph3_Yh3 ƶ%src/raklib/server/UDPServerSocket.php _Y BH%src/spl/ArrayOutOfBoundsException.php_Y(_hsrc/spl/AttachableLogger.php_Yuw$src/spl/AttachableThreadedLogger.php_Ybsrc/spl/BaseClassLoader.php_Y gsrc/spl/ClassCastException.php_YL4;src/spl/ClassLoader.php_YI|"src/spl/ClassNotFoundException.php_Y/)src/spl/InvalidArgumentCountException.php_Ysꢶsrc/spl/InvalidKeyException.php_Yz!src/spl/InvalidStateException.php_Y'^src/spl/LogLevel.phpq_YqI¶src/spl/Logger.php_Y esrc/spl/LoggerAttachment.php_YHsrc/spl/SplFixedByteArray.php, _Y, E*&src/spl/StringOutOfBoundsException.php_Ysrc/spl/ThreadException.php_YH-src/spl/ThreadedLogger.php_Y$src/spl/ThreadedLoggerAttachment.php_Y&src/spl/UndefinedConstantException.php_YkGS&src/spl/UndefinedPropertyException.php_YC]&src/spl/UndefinedVariableException.php_YMsrc/spl/stubs/leveldb.php_Yq4Sնsrc/spl/stubs/pthreads.phpb/_Yb/B src/spl/stubs/weakref.php% _Y% j8Zsrc/spl/stubs/yaml.php _Y #ڶ8f82730b9899c6d41b820f62ad8e60c4d829c607 8f82730b9899c6d41b820f62ad8e60c4d829c607 array( "name" => "Taking Inventory", "requires" => [], ),*/ "mineWood" => [ "name" => "Getting Wood", "requires" => [ //"openInventory", ], ], "buildWorkBench" => [ "name" => "Benchmarking", "requires" => [ "mineWood", ], ], "buildPickaxe" => [ "name" => "Time to Mine!", "requires" => [ "buildWorkBench", ], ], "buildFurnace" => [ "name" => "Hot Topic", "requires" => [ "buildPickaxe", ], ], "acquireIron" => [ "name" => "Acquire hardware", "requires" => [ "buildFurnace", ], ], "buildHoe" => [ "name" => "Time to Farm!", "requires" => [ "buildWorkBench", ], ], "makeBread" => [ "name" => "Bake Bread", "requires" => [ "buildHoe", ], ], "bakeCake" => [ "name" => "The Lie", "requires" => [ "buildHoe", ], ], "buildBetterPickaxe" => [ "name" => "Getting an Upgrade", "requires" => [ "buildPickaxe", ], ], "buildSword" => [ "name" => "Time to Strike!", "requires" => [ "buildWorkBench", ], ], "diamonds" => [ "name" => "DIAMONDS!", "requires" => [ "acquireIron", ], ], ]; /** * @param Player $player * @param $achievementId * * @return bool */ public static function broadcast(Player $player, $achievementId){ if(isset(Achievement::$list[$achievementId])){ $translation = new TranslationContainer("chat.type.achievement", [$player->getDisplayName(), TextFormat::GREEN . Achievement::$list[$achievementId]["name"]]); if(Server::getInstance()->getConfigString("announce-player-achievements", true) === true){ Server::getInstance()->broadcastMessage($translation); }else{ $player->sendMessage($translation); } return true; } return false; } /** * @param $achievementId * @param $achievementName * @param array $requires * * @return bool */ public static function add($achievementId, $achievementName, array $requires = []){ if(!isset(Achievement::$list[$achievementId])){ Achievement::$list[$achievementId] = [ "name" => $achievementName, "requires" => $requires, ]; return true; } return false; } } time = time(); $this->server = $server; $this->path = $this->server->getCrashPath() . "CrashDump_" . date("D_M_j-H.i.s-T_Y", $this->time) . ".log"; $this->fp = @fopen($this->path, "wb"); if(!is_resource($this->fp)){ throw new \RuntimeException("Could not create Crash Dump"); } $this->data["time"] = $this->time; $this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", $this->time)); $this->addLine(); try{ $this->baseCrash(); }catch(\Exception $e){ //Attempt to fix incomplete crashdumps $this->addLine("CrashDump crashed while generating base crash data"); $this->addLine(); } $this->generalData(); $this->pluginsData(); $this->extraData(); //$this->encodeData(); } /** * @return string */ public function getPath(){ return $this->path; } /** * @return null */ public function getEncodedData(){ return $this->encodedData; } /** * @return array */ public function getData(){ return $this->data; } private function pluginsData(){ if(class_exists("pocketmine\\plugin\\PluginManager", false)){ $this->addLine(); $this->addLine("Loaded plugins:"); $this->data["plugins"] = []; foreach($this->server->getPluginManager()->getPlugins() as $p){ $d = $p->getDescription(); $this->data["plugins"][$d->getName()] = [ "name" => $d->getName(), "version" => $d->getVersion(), "authors" => $d->getAuthors(), "api" => $d->getCompatibleApis(), "enabled" => $p->isEnabled(), "depends" => $d->getDepend(), "softDepends" => $d->getSoftDepend(), "main" => $d->getMain(), "load" => $d->getOrder() === PluginLoadOrder::POSTWORLD ? "POSTWORLD" : "STARTUP", "website" => $d->getWebsite() ]; $this->addLine($d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . " for API(s) " . implode(", ", $d->getCompatibleApis())); } } } private function extraData(){ global $arguments; if($this->server->getProperty("auto-report.send-settings", true) !== false){ $this->data["parameters"] = (array) $arguments; $this->data["server.properties"] = @file_get_contents($this->server->getDataPath() . "server.properties"); $this->data["server.properties"] = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $this->data["server.properties"]); $this->data["pocketmine.yml"] = @file_get_contents($this->server->getDataPath() . "pocketmine.yml"); }else{ $this->data["pocketmine.yml"] = ""; $this->data["server.properties"] = ""; $this->data["parameters"] = []; } $extensions = []; foreach(get_loaded_extensions() as $ext){ $extensions[$ext] = phpversion($ext); } $this->data["extensions"] = $extensions; if($this->server->getProperty("auto-report.send-phpinfo", true) !== false){ ob_start(); phpinfo(); $this->data["phpinfo"] = ob_get_contents(); ob_end_clean(); } } private function baseCrash(){ global $lastExceptionError, $lastError; if(isset($lastExceptionError)){ $error = $lastExceptionError; }else{ $error = (array) error_get_last(); $error["trace"] = @getTrace(3); $errorConversion = [ E_ERROR => "E_ERROR", E_WARNING => "E_WARNING", E_PARSE => "E_PARSE", E_NOTICE => "E_NOTICE", E_CORE_ERROR => "E_CORE_ERROR", E_CORE_WARNING => "E_CORE_WARNING", E_COMPILE_ERROR => "E_COMPILE_ERROR", E_COMPILE_WARNING => "E_COMPILE_WARNING", E_USER_ERROR => "E_USER_ERROR", E_USER_WARNING => "E_USER_WARNING", E_USER_NOTICE => "E_USER_NOTICE", E_STRICT => "E_STRICT", E_RECOVERABLE_ERROR => "E_RECOVERABLE_ERROR", E_DEPRECATED => "E_DEPRECATED", E_USER_DEPRECATED => "E_USER_DEPRECATED", ]; $error["fullFile"] = $error["file"]; $error["file"] = cleanPath($error["file"]); $error["type"] = isset($errorConversion[$error["type"]]) ? $errorConversion[$error["type"]] : $error["type"]; if(($pos = strpos($error["message"], "\n")) !== false){ $error["message"] = substr($error["message"], 0, $pos); } } if(isset($lastError)){ $this->data["lastError"] = $lastError; } $this->data["error"] = $error; unset($this->data["error"]["fullFile"]); unset($this->data["error"]["trace"]); $this->addLine("Error: " . $error["message"]); $this->addLine("File: " . $error["file"]); $this->addLine("Line: " . $error["line"]); $this->addLine("Type: " . $error["type"]); if(strpos($error["file"], "src/pocketmine/") === false and strpos($error["file"], "src/raklib/") === false and file_exists($error["fullFile"])){ $this->addLine(); $this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN"); $this->data["plugin"] = true; $reflection = new \ReflectionClass(PluginBase::class); $file = $reflection->getProperty("file"); $file->setAccessible(true); foreach($this->server->getPluginManager()->getPlugins() as $plugin){ $filePath = \pocketmine\cleanPath($file->getValue($plugin)); if(strpos($error["file"], $filePath) === 0){ $this->data["plugin"] = $plugin->getName(); $this->addLine("BAD PLUGIN : " . $plugin->getDescription()->getFullName()); break; } } }else{ $this->data["plugin"] = false; } $this->addLine(); $this->addLine("Code:"); $this->data["code"] = []; if($this->server->getProperty("auto-report.send-code", true) !== false){ $file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES); for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10; ++$l){ $this->addLine("[" . ($l + 1) . "] " . @$file[$l]); $this->data["code"][$l + 1] = @$file[$l]; } } $this->addLine(); $this->addLine("Backtrace:"); foreach(($this->data["trace"] = $error["trace"]) as $line){ $this->addLine($line); } $this->addLine(); } private function generalData(){ $this->data["general"] = []; $this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL; $this->data["general"]["api"] = \pocketmine\API_VERSION; $this->data["general"]["git"] = \pocketmine\GIT_COMMIT; $this->data["general"]["raklib"] = RakLib::VERSION; $this->data["general"]["uname"] = php_uname("a"); $this->data["general"]["php"] = phpversion(); $this->data["general"]["zend"] = zend_version(); $this->data["general"]["php_os"] = PHP_OS; $this->data["general"]["os"] = Utils::getOS(); $this->addLine($this->server->getName(). " version: " . \pocketmine\GIT_COMMIT . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "; API " . API_VERSION . "]"); $this->addLine("uname -a: " . php_uname("a")); $this->addLine("PHP version: " . phpversion()); $this->addLine("Zend version: " . zend_version()); $this->addLine("OS : " . PHP_OS . ", " . Utils::getOS()); $this->addLine(); $this->addLine("Server uptime: " . $this->server->getUptime()); $this->addLine("Number of loaded worlds: " . count($this->server->getLevels())); $this->addLine("Players online: " . count($this->server->getOnlinePlayers()) . "/" . $this->server->getMaxPlayers()); } /** * @param string $line */ public function addLine($line = ""){ fwrite($this->fp, $line . PHP_EOL); } /** * @param $str */ public function add($str){ fwrite($this->fp, $str); } } server = $server; $this->init(); } private function init(){ $this->memoryLimit = ((int) $this->server->getProperty("memory.main-limit", 0)) * 1024 * 1024; $defaultMemory = 1024; if(preg_match("/([0-9]+)([KMGkmg])/", $this->server->getConfigString("memory-limit", ""), $matches) > 0){ $m = (int) $matches[1]; if($m <= 0){ $defaultMemory = 0; }else{ switch(strtoupper($matches[2])){ case "K": $defaultMemory = $m / 1024; break; case "M": $defaultMemory = $m; break; case "G": $defaultMemory = $m * 1024; break; default: $defaultMemory = $m; break; } } } $hardLimit = ((int) $this->server->getProperty("memory.main-hard-limit", $defaultMemory)); if($hardLimit <= 0){ ini_set("memory_limit", -1); }else{ ini_set("memory_limit", $hardLimit . "M"); } $this->globalMemoryLimit = ((int) $this->server->getProperty("memory.global-limit", 0)) * 1024 * 1024; $this->checkRate = (int) $this->server->getProperty("memory.check-rate", 20); $this->continuousTrigger = (bool) $this->server->getProperty("memory.continuous-trigger", true); $this->continuousTriggerRate = (int) $this->server->getProperty("memory.continuous-trigger-rate", 30); $this->garbageCollectionPeriod = (int) $this->server->getProperty("memory.garbage-collection.period", 36000); $this->garbageCollectionTrigger = (bool) $this->server->getProperty("memory.garbage-collection.low-memory-trigger", true); $this->garbageCollectionAsync = (bool) $this->server->getProperty("memory.garbage-collection.collect-async-worker", true); $this->chunkRadiusOverride = (int) $this->server->getProperty("memory.max-chunks.chunk-radius", 4); $this->chunkCollect = (bool) $this->server->getProperty("memory.max-chunks.trigger-chunk-collect", true); $this->chunkTrigger = (bool) $this->server->getProperty("memory.max-chunks.low-memory-trigger", true); $this->chunkCache = (bool) $this->server->getProperty("memory.world-caches.disable-chunk-cache", true); $this->cacheTrigger = (bool) $this->server->getProperty("memory.world-caches.low-memory-trigger", true); gc_enable(); } /** * @return bool */ public function isLowMemory(){ return $this->lowMemory; } /** * @return bool */ public function canUseChunkCache(){ return !($this->lowMemory and $this->chunkTrigger); } /** * Returns the allowed chunk radius based on the current memory usage. * * @param int $distance * * @return int */ public function getViewDistance(int $distance) : int{ return $this->lowMemory ? min($this->chunkRadiusOverride, $distance) : $distance; } /** * @param $memory * @param $limit * @param bool $global * @param int $triggerCount */ public function trigger($memory, $limit, $global = false, $triggerCount = 0){ $this->server->getLogger()->debug("[Memory Manager] " . ($global ? "Global " : "") . "Low memory triggered, limit " . round(($limit / 1024) / 1024, 2) . "MB, using " . round(($memory / 1024) / 1024, 2) . "MB"); if($this->cacheTrigger){ foreach($this->server->getLevels() as $level){ $level->clearCache(true); } } if($this->chunkTrigger and $this->chunkCollect){ foreach($this->server->getLevels() as $level){ $level->doChunkGarbageCollection(); } } $ev = new LowMemoryEvent($memory, $limit, $global, $triggerCount); $this->server->getPluginManager()->callEvent($ev); $cycles = 0; if($this->garbageCollectionTrigger){ $cycles = $this->triggerGarbageCollector(); } $this->server->getLogger()->debug("[Memory Manager] Freed " . round(($ev->getMemoryFreed() / 1024) / 1024, 2) . "MB, $cycles cycles"); } public function check(){ Timings::$memoryManagerTimer->startTiming(); if(($this->memoryLimit > 0 or $this->globalMemoryLimit > 0) and ++$this->checkTicker >= $this->checkRate){ $this->checkTicker = 0; $memory = Utils::getMemoryUsage(true); $trigger = false; if($this->memoryLimit > 0 and $memory[0] > $this->memoryLimit){ $trigger = 0; }elseif($this->globalMemoryLimit > 0 and $memory[1] > $this->globalMemoryLimit){ $trigger = 1; } if($trigger !== false){ if($this->lowMemory and $this->continuousTrigger){ if(++$this->continuousTriggerTicker >= $this->continuousTriggerRate){ $this->continuousTriggerTicker = 0; $this->trigger($memory[$trigger], $this->memoryLimit, $trigger > 0, ++$this->continuousTriggerCount); } }else{ $this->lowMemory = true; $this->continuousTriggerCount = 0; $this->trigger($memory[$trigger], $this->memoryLimit, $trigger > 0); } }else{ $this->lowMemory = false; } } if($this->garbageCollectionPeriod > 0 and ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){ $this->garbageCollectionTicker = 0; $this->triggerGarbageCollector(); } Timings::$memoryManagerTimer->stopTiming(); } /** * @return int */ public function triggerGarbageCollector(){ Timings::$garbageCollectorTimer->startTiming(); if($this->garbageCollectionAsync){ $size = $this->server->getScheduler()->getAsyncTaskPoolSize(); for($i = 0; $i < $size; ++$i){ $this->server->getScheduler()->scheduleAsyncTaskToWorker(new GarbageCollectionTask(), $i); } } $cycles = gc_collect_cycles(); foreach($this->server->getLevels() as $level){ $level->doChunkGarbageCollection(); } Timings::$garbageCollectorTimer->stopTiming(); return $cycles; } /** * @param object $object * * @return string Object identifier for future checks */ public function addObjectWatcher($object){ if(!is_object($object)){ throw new \InvalidArgumentException("Not an object!"); } $identifier = spl_object_hash($object) . ":" . get_class($object); if(isset($this->leakInfo[$identifier])){ return $this->leakInfo["id"]; } $this->leakInfo[$identifier] = [ "id" => $id = md5($identifier . ":" . $this->leakSeed++), "class" => get_class($object), "hash" => $identifier ]; $this->leakInfo[$id] = $this->leakInfo[$identifier]; $this->leakWatch[$id] = new \WeakRef($object); return $id; } /** * @param $id * * @return bool */ public function isObjectAlive($id){ if(isset($this->leakWatch[$id])){ return $this->leakWatch[$id]->valid(); } return false; } /** * @param $id */ public function removeObjectWatch($id){ if(!isset($this->leakWatch[$id])){ return; } unset($this->leakInfo[$this->leakInfo[$id]["hash"]]); unset($this->leakInfo[$id]); unset($this->leakWatch[$id]); } public function doObjectCleanup(){ foreach($this->leakWatch as $id => $w){ if(!$w->valid()){ $this->removeObjectWatch($id); } } } /** * @param $id * @param bool $includeObject * * @return array|null */ public function getObjectInformation($id, $includeObject = false){ if(!isset($this->leakWatch[$id])){ return null; } $valid = false; $references = 0; $object = null; if($this->leakWatch[$id]->acquire()){ $object = $this->leakWatch[$id]->get(); $this->leakWatch[$id]->release(); $valid = true; $references = getReferenceCount($object, false); } return [ "id" => $id, "class" => $this->leakInfo[$id]["class"], "hash" => $this->leakInfo[$id]["hash"], "valid" => $valid, "references" => $references, "object" => $includeObject ? $object : null ]; } /** * @param $outputFolder * @param $maxNesting * @param $maxStringSize */ public function dumpServerMemory($outputFolder, $maxNesting, $maxStringSize){ gc_disable(); ini_set("memory_limit", -1); if(!file_exists($outputFolder)){ mkdir($outputFolder, 0777, true); } $this->server->getLogger()->notice("[Dump] After the memory dump is done, the server will shut down"); $obData = fopen($outputFolder . "/objects.js", "wb+"); $staticProperties = []; $data = []; $objects = []; $refCounts = []; $this->continueDump($this->server, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize); do{ $continue = false; foreach($objects as $hash => $object){ if(!is_object($object)){ continue; } $continue = true; $className = get_class($object); $objects[$hash] = true; $reflection = new \ReflectionObject($object); $info = [ "information" => "$hash@$className", "properties" => [] ]; if($reflection->getParentClass()){ $info["parent"] = $reflection->getParentClass()->getName(); } if(count($reflection->getInterfaceNames()) > 0){ $info["implements"] = implode(", ", $reflection->getInterfaceNames()); } foreach($reflection->getProperties() as $property){ if($property->isStatic()){ continue; } if(!$property->isPublic()){ $property->setAccessible(true); } $this->continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize); } fwrite($obData, "$hash@$className: " . json_encode($info, JSON_UNESCAPED_SLASHES) . "\n"); if(!isset($objects["staticProperties"][$className])){ $staticProperties[$className] = []; foreach($reflection->getProperties() as $property){ if(!$property->isStatic() or $property->getDeclaringClass()->getName() !== $className){ continue; } if(!$property->isPublic()){ $property->setAccessible(true); } $this->continueDump($property->getValue($object), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize); } } } echo "[Dump] Wrote " . count($objects) . " objects\n"; }while($continue); fclose($obData); file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents($outputFolder . "/serverEntry.js", json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); file_put_contents($outputFolder . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); echo "[Dump] Finished!\n"; gc_enable(); } /** * @param $from * @param $data * @param $objects * @param $refCounts * @param $recursion * @param $maxNesting * @param $maxStringSize */ private function continueDump($from, &$data, &$objects, &$refCounts, $recursion, $maxNesting, $maxStringSize){ if($maxNesting <= 0){ $data = "(error) NESTING LIMIT REACHED"; return; } --$maxNesting; if(is_object($from)){ if(!isset($objects[$hash = spl_object_hash($from)])){ $objects[$hash] = $from; $refCounts[$hash] = 0; } ++$refCounts[$hash]; $data = "(object) $hash@" . get_class($from); }elseif(is_array($from)){ if($recursion >= 5){ $data = "(error) ARRAY RECURSION LIMIT REACHED"; return; } $data = []; foreach($from as $key => $value){ $this->continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize); } }elseif(is_string($from)){ $data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize); }elseif(is_resource($from)){ $data = "(resource) " . print_r($from, true); }else{ $data = $from; } } }server = $server; $this->name = $name; if(file_exists($this->server->getDataPath() . "players/" . strtolower($this->getName()) . ".dat")){ $this->namedtag = $this->server->getOfflinePlayerData($this->name); }else{ $this->namedtag = null; } } /** * @return bool */ public function isOnline(){ return $this->getPlayer() !== null; } /** * @return string */ public function getName() : string{ return $this->name; } /** * @return Server */ public function getServer(){ return $this->server; } /** * @return bool */ public function isOp(){ return $this->server->isOp(strtolower($this->getName())); } /** * @param bool $value */ public function setOp($value){ if($value === $this->isOp()){ return; } if($value === true){ $this->server->addOp(strtolower($this->getName())); }else{ $this->server->removeOp(strtolower($this->getName())); } } /** * @return bool */ public function isBanned(){ return $this->server->getNameBans()->isBanned(strtolower($this->getName())); } /** * @param bool $value */ public function setBanned($value){ if($value === true){ $this->server->getNameBans()->addBan($this->getName(), null, null, null); }else{ $this->server->getNameBans()->remove($this->getName()); } } /** * @return bool */ public function isWhitelisted(){ return $this->server->isWhitelisted(strtolower($this->getName())); } /** * @param bool $value */ public function setWhitelisted($value){ if($value === true){ $this->server->addWhitelist(strtolower($this->getName())); }else{ $this->server->removeWhitelist(strtolower($this->getName())); } } /** * @return Player */ public function getPlayer(){ return $this->server->getPlayerExact($this->getName()); } /** * @return null */ public function getFirstPlayed(){ return $this->namedtag instanceof CompoundTag ? $this->namedtag["firstPlayed"] : null; } /** * @return null */ public function getLastPlayed(){ return $this->namedtag instanceof CompoundTag ? $this->namedtag["lastPlayed"] : null; } /** * @return bool */ public function hasPlayedBefore(){ return $this->namedtag instanceof CompoundTag; } /** * @param string $metadataKey * @param MetadataValue $metadataValue */ public function setMetadata($metadataKey, MetadataValue $metadataValue){ $this->server->getPlayerMetadata()->setMetadata($this, $metadataKey, $metadataValue); } /** * @param string $metadataKey * * @return MetadataValue[] */ public function getMetadata($metadataKey){ return $this->server->getPlayerMetadata()->getMetadata($this, $metadataKey); } /** * @param string $metadataKey * * @return bool */ public function hasMetadata($metadataKey){ return $this->server->getPlayerMetadata()->hasMetadata($this, $metadataKey); } /** * @param string $metadataKey * @param Plugin $plugin */ public function removeMetadata($metadataKey, Plugin $plugin){ $this->server->getPlayerMetadata()->removeMetadata($this, $metadataKey, $plugin); } } */ protected $windows; /** @var Inventory[] */ protected $windowIndex = []; protected $messageCounter = 2; private $clientSecret; /** @var Vector3 */ public $speed = null; public $achievements = []; public $craftingType = self::CRAFTING_SMALL; //0 = 2x2 crafting, 1 = 3x3 crafting, 2 = anvil, 3 = enchanting public $creationTime = 0; protected $randomClientId; protected $protocol; /** @var Vector3 */ protected $forceMovement = null; /** @var Vector3 */ protected $teleportPosition = null; protected $connected = true; protected $ip; protected $removeFormat = false; protected $port; protected $username; protected $iusername; protected $displayName; protected $startAction = -1; /** @var Vector3 */ protected $sleeping = null; protected $clientID = null; protected $deviceModel; protected $deviceOS; private $loaderId = null; protected $stepHeight = 0.6; public $usedChunks = []; protected $chunkLoadCount = 0; protected $loadQueue = []; protected $nextChunkOrderRun = 5; /** @var Player[] */ protected $hiddenPlayers = []; /** @var Vector3 */ protected $newPosition; protected $viewDistance = -1; protected $chunksPerTick; protected $spawnThreshold; /** @var null|WeakPosition */ private $spawnPosition = null; protected $inAirTicks = 0; protected $startAirTicks = 5; //TODO: Abilities protected $autoJump = true; protected $allowFlight = false; protected $flying = false; protected $allowMovementCheats = false; protected $allowInstaBreak = false; private $needACK = []; private $batchedPackets = []; /** @var PermissibleBase */ private $perm = null; public $weatherData = [0, 0, 0]; /** @var Vector3 */ public $fromPos = null; private $portalTime = 0; protected $shouldSendStatus = false; /** @var Position */ private $shouldResPos; /** @var FishingHook */ public $fishingHook = null; /** @var Position[] */ public $selectedPos = []; /** @var Level[] */ public $selectedLev = []; /** @var Item[] */ protected $personalCreativeItems = []; /** @var int */ protected $lastEnderPearlUse = 0; /** * @param FishingHook $entity * * @return bool */ public function linkHookToPlayer(FishingHook $entity){ if($entity->isAlive()){ $this->setFishingHook($entity); $pk = new EntityEventPacket(); $pk->eid = $this->getFishingHook()->getId(); $pk->event = EntityEventPacket::FISH_HOOK_POSITION; $this->server->broadcastPacket($this->level->getPlayers(), $pk); return true; } return false; } /** * @return bool */ public function unlinkHookFromPlayer(){ if($this->fishingHook instanceof FishingHook){ $pk = new EntityEventPacket(); $pk->eid = $this->fishingHook->getId(); $pk->event = EntityEventPacket::FISH_HOOK_TEASE; $this->server->broadcastPacket($this->level->getPlayers(), $pk); $this->setFishingHook(); return true; } return false; } /** * @return bool */ public function isFishing(){ return ($this->fishingHook instanceof FishingHook); } /** * @return FishingHook */ public function getFishingHook(){ return $this->fishingHook; } /** * @param FishingHook|null $entity */ public function setFishingHook(FishingHook $entity = null){ if($entity == null and $this->fishingHook instanceof FishingHook){ $this->fishingHook->close(); } $this->fishingHook = $entity; } /** * @return mixed */ public function getDeviceModel(){ return $this->deviceModel; } /** * @return mixed */ public function getDeviceOS(){ return $this->deviceOS; } /** * @return Item */ public function getItemInHand(){ return $this->inventory->getItemInHand(); } /** * @return TranslationContainer */ public function getLeaveMessage(){ return new TranslationContainer(TextFormat::YELLOW . "%multiplayer.player.left", [ $this->getDisplayName() ]); } /** * This might disappear in the future. * Please use getUniqueId() instead (IP + clientId + name combo, in the future it'll change to real UUID for online * auth) */ public function getClientId(){ return $this->randomClientId; } /** * @return mixed */ public function getClientSecret(){ return $this->clientSecret; } /** * @return bool */ public function isBanned(){ return $this->server->getNameBans()->isBanned(strtolower($this->getName())); } /** * @param bool $value */ public function setBanned($value){ if($value === true){ $this->server->getNameBans()->addBan($this->getName(), null, null, null); $this->kick(TextFormat::RED . "You have been banned"); }else{ $this->server->getNameBans()->remove($this->getName()); } } /** * @return bool */ public function isWhitelisted() : bool{ return $this->server->isWhitelisted(strtolower($this->getName())); } /** * @param bool $value */ public function setWhitelisted($value){ if($value === true){ $this->server->addWhitelist(strtolower($this->getName())); }else{ $this->server->removeWhitelist(strtolower($this->getName())); } } /** * @return $this */ public function getPlayer(){ return $this; } /** * @return null */ public function getFirstPlayed(){ return $this->namedtag instanceof CompoundTag ? $this->namedtag["firstPlayed"] : null; } /** * @return null */ public function getLastPlayed(){ return $this->namedtag instanceof CompoundTag ? $this->namedtag["lastPlayed"] : null; } /** * @return bool */ public function hasPlayedBefore(){ return $this->playedBefore; } /** * @param $value */ public function setAllowFlight($value){ $this->allowFlight = (bool) $value; $this->sendSettings(); } /** * @return bool */ public function getAllowFlight() : bool{ return $this->allowFlight; } /** * @param bool $value */ public function setFlying(bool $value){ $this->flying = $value; $this->sendSettings(); } /** * @return bool */ public function isFlying() : bool{ return $this->flying; } /** * @param $value */ public function setAutoJump($value){ $this->autoJump = $value; $this->sendSettings(); } /** * @return bool */ public function hasAutoJump() : bool{ return $this->autoJump; } /** * @return bool */ public function allowMovementCheats() : bool{ return $this->allowMovementCheats; } /** * @param bool $value */ public function setAllowMovementCheats(bool $value = false){ $this->allowMovementCheats = $value; } /** * @return bool */ public function allowInstaBreak() : bool{ return $this->allowInstaBreak; } /** * @param bool $value */ public function setAllowInstaBreak(bool $value = false){ $this->allowInstaBreak = $value; } /** * @param Player $player */ public function spawnTo(Player $player){ if($this->spawned and $player->spawned and $this->isAlive() and $player->isAlive() and $player->getLevel() === $this->level and $player->canSee($this) and !$this->isSpectator()){ parent::spawnTo($player); } } /** * @return Server */ public function getServer(){ return $this->server; } /** * @return bool */ public function getRemoveFormat(){ return $this->removeFormat; } /** * @param bool $remove */ public function setRemoveFormat($remove = true){ $this->removeFormat = (bool) $remove; } /** * @param Player $player * * @return bool */ public function canSee(Player $player) : bool{ return !isset($this->hiddenPlayers[$player->getRawUniqueId()]); } /** * @param Player $player */ public function hidePlayer(Player $player){ if($player === $this){ return; } $this->hiddenPlayers[$player->getRawUniqueId()] = $player; $player->despawnFrom($this); } /** * @param Player $player */ public function showPlayer(Player $player){ if($player === $this){ return; } unset($this->hiddenPlayers[$player->getRawUniqueId()]); if($player->isOnline()){ $player->spawnTo($this); } } /** * @param Entity $entity * * @return bool */ public function canCollideWith(Entity $entity) : bool{ return false; } public function resetFallDistance(){ parent::resetFallDistance(); if($this->inAirTicks !== 0){ $this->startAirTicks = 5; } $this->inAirTicks = 0; } /** * @return int */ public function getViewDistance() : int{ return $this->viewDistance; } /** * @param int $distance */ public function setViewDistance(int $distance){ $this->viewDistance = $this->server->getAllowedViewDistance($distance); $this->spawnThreshold = (int) (min($this->viewDistance, $this->server->getProperty("chunk-sending.spawn-radius", 4)) ** 2 * M_PI); $pk = new ChunkRadiusUpdatedPacket(); $pk->radius = $this->viewDistance; $this->dataPacket($pk); } /** * @return bool */ public function isOnline() : bool{ return $this->connected === true and $this->loggedIn === true; } /** * @return bool */ public function isOp() : bool{ return $this->server->isOp($this->getName()); } /** * @param bool $value */ public function setOp($value){ if($value === $this->isOp()){ return; } if($value === true){ $this->server->addOp($this->getName()); }else{ $this->server->removeOp($this->getName()); } $this->recalculatePermissions(); $this->sendSettings(); } /** * @param permission\Permission|string $name * * @return bool */ public function isPermissionSet($name){ return $this->perm->isPermissionSet($name); } /** * @param permission\Permission|string $name * * @return bool * * @throws \InvalidStateException if the player is closed */ public function hasPermission($name){ if($this->closed){ throw new \InvalidStateException("Trying to get permissions of closed player"); } return $this->perm->hasPermission($name); } /** * @param Plugin $plugin * @param string $name * @param bool $value * * @return permission\PermissionAttachment|null */ public function addAttachment(Plugin $plugin, $name = null, $value = null){ if($this->perm == null) return null; return $this->perm->addAttachment($plugin, $name, $value); } /** * @param PermissionAttachment $attachment * * @return bool */ public function removeAttachment(PermissionAttachment $attachment){ if($this->perm == null){ return false; } $this->perm->removeAttachment($attachment); return true; } public function recalculatePermissions(){ $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this); $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); if($this->perm === null){ return; } $this->perm->recalculatePermissions(); if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){ $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this); } if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){ $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); } $this->sendCommandData(); } /** * @return permission\PermissionAttachmentInfo[] */ public function getEffectivePermissions(){ return $this->perm->getEffectivePermissions(); } public function sendCommandData(){ $data = new \stdClass(); $count = 0; foreach($this->server->getCommandMap()->getCommands() as $command){ //if($this->hasPermission($command->getPermission()) or $command->getPermission() == null) { if (($cmdData = $command->generateCustomCommandData($this)) !== null){ ++$count; $data->{$command->getName()}->versions[0] = $cmdData; } //} } if($count > 0){ //TODO: structure checking $pk = new AvailableCommandsPacket(); $pk->commands = json_encode($data); $this->dataPacket($pk); } } /** * @param SourceInterface $interface * @param null $clientID * @param string $ip * @param int $port */ public function __construct(SourceInterface $interface, $clientID, $ip, $port){ $this->interface = $interface; $this->windows = new \SplObjectStorage(); $this->perm = new PermissibleBase($this); $this->namedtag = new CompoundTag(); $this->server = Server::getInstance(); $this->lastBreak = PHP_INT_MAX; $this->ip = $ip; $this->port = $port; $this->clientID = $clientID; $this->loaderId = Level::generateChunkLoaderId($this); $this->chunksPerTick = (int) $this->server->getProperty("chunk-sending.per-tick", 4); $this->spawnThreshold = (int) (($this->server->getProperty("chunk-sending.spawn-radius", 4) ** 2) * M_PI); $this->spawnPosition = null; $this->gamemode = $this->server->getGamemode(); $this->setLevel($this->server->getDefaultLevel()); $this->newPosition = new Vector3(0, 0, 0); $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); $this->uuid = null; $this->rawUUID = null; $this->creationTime = microtime(true); $this->allowMovementCheats = (bool) $this->server->getProperty("player.anti-cheat.allow-movement-cheats", false); $this->allowInstaBreak = (bool) $this->server->getProperty("player.anti-cheat.allow-instabreak", false); } /** * @param string $achievementId */ public function removeAchievement($achievementId){ if($this->hasAchievement($achievementId)){ $this->achievements[$achievementId] = false; } } /** * @param string $achievementId * * @return bool */ public function hasAchievement($achievementId) : bool{ if(!isset(Achievement::$list[$achievementId]) or !isset($this->achievements)){ $this->achievements = []; return false; } return isset($this->achievements[$achievementId]) and $this->achievements[$achievementId] != false; } /** * @return bool */ public function isConnected() : bool{ return $this->connected === true; } /** * Gets the "friendly" name to display of this player to use in the chat. * * @return string */ public function getDisplayName(){ return $this->displayName; } /** * @param string $name */ public function setDisplayName($name){ $this->displayName = $name; if($this->spawned){ $this->server->updatePlayerListData($this->getUniqueId(), $this->getId(), $this->getDisplayName(), $this->getSkinId(), $this->getSkinData()); } } /** * @param string $str * @param string $skinId */ public function setSkin($str, $skinId){ parent::setSkin($str, $skinId); if($this->spawned){ $this->server->updatePlayerListData($this->getUniqueId(), $this->getId(), $this->getDisplayName(), $skinId, $str); } } /** * Gets the player IP address * * @return string */ public function getAddress() : string{ return $this->ip; } /** * @return int */ public function getPort() : int{ return $this->port; } /** * @return Position */ public function getNextPosition(){ return $this->newPosition !== null ? new Position($this->newPosition->x, $this->newPosition->y, $this->newPosition->z, $this->level) : $this->getPosition(); } /** * @return bool */ public function isSleeping() : bool{ return $this->sleeping !== null; } /** * @return int */ public function getInAirTicks(){ return $this->inAirTicks; } /** * @param Level $targetLevel * * @return bool|void */ protected function switchLevel(Level $targetLevel){ $oldLevel = $this->level; if(parent::switchLevel($targetLevel)){ foreach($this->usedChunks as $index => $d){ Level::getXZ($index, $X, $Z); $this->unloadChunk($X, $Z, $oldLevel); } $this->usedChunks = []; $pk = new SetTimePacket(); $pk->time = $this->level->getTime(); $pk->started = $this->level->stopTime == false; $this->dataPacket($pk); if($targetLevel->getDimension() != $oldLevel->getDimension()){ $pk = new ChangeDimensionPacket(); $pk->dimension = $targetLevel->getDimension(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $this->dataPacket($pk); //$this->shouldSendStatus = true; $pk1 = new PlayStatusPacket(); $pk1->status = PlayStatusPacket::PLAYER_SPAWN; $this->dataPacket($pk1); } $targetLevel->getWeather()->sendWeather($this); if($this->spawned){ $this->spawnToAll(); } } } /** * @param $x * @param $z * @param Level|null $level */ private function unloadChunk($x, $z, Level $level = null){ $level = $level === null ? $this->level : $level; $index = Level::chunkHash($x, $z); if(isset($this->usedChunks[$index])){ foreach($level->getChunkEntities($x, $z) as $entity){ if($entity !== $this){ $entity->despawnFrom($this); } } unset($this->usedChunks[$index]); } $level->unregisterChunkLoader($this, $x, $z); unset($this->loadQueue[$index]); } /** * @return Position */ public function getSpawn(){ if($this->hasValidSpawnPosition()){ return $this->spawnPosition; }else{ $level = $this->server->getDefaultLevel(); return $level->getSafeSpawn(); } } /** * @return bool */ public function hasValidSpawnPosition() : bool{ return $this->spawnPosition instanceof WeakPosition and $this->spawnPosition->isValid(); } /** * @param $x * @param $z * @param $payload */ public function sendChunk($x, $z, $payload){ if($this->connected === false){ return; } $this->usedChunks[Level::chunkHash($x, $z)] = true; $this->chunkLoadCount++; if($payload instanceof DataPacket){ $this->dataPacket($payload); }else{ $pk = new FullChunkDataPacket(); $pk->chunkX = $x; $pk->chunkZ = $z; $pk->data = $payload; $this->batchDataPacket($pk); } if($this->spawned){ foreach($this->level->getChunkEntities($x, $z) as $entity){ if($entity !== $this and !$entity->closed and $entity->isAlive()){ $entity->spawnTo($this); } } } } protected function sendNextChunk(){ if($this->connected === false){ return; } Timings::$playerChunkSendTimer->startTiming(); $count = 0; foreach($this->loadQueue as $index => $distance){ if($count >= $this->chunksPerTick){ break; } $X = null; $Z = null; Level::getXZ($index, $X, $Z); ++$count; $this->usedChunks[$index] = false; $this->level->registerChunkLoader($this, $X, $Z, false); if(!$this->level->populateChunk($X, $Z)){ continue; } unset($this->loadQueue[$index]); $this->level->requestChunk($X, $Z, $this); } if($this->chunkLoadCount >= $this->spawnThreshold and $this->spawned === false and $this->teleportPosition === null){ $this->doFirstSpawn(); } Timings::$playerChunkSendTimer->stopTiming(); } protected function doFirstSpawn(){ $this->spawned = true; $this->sendPotionEffects($this); $this->sendData($this); $pk = new SetTimePacket(); $pk->time = $this->level->getTime(); $pk->started = $this->level->stopTime == false; $this->dataPacket($pk); $pos = $this->level->getSafeSpawn($this); $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $pos)); $pos = $ev->getRespawnPosition(); if($pos->getY() < 127) $pos = $pos->add(0, 0.2, 0); /*$pk = new RespawnPacket(); $pk->x = $pos->x; $pk->y = $pos->y; $pk->z = $pos->z; $this->dataPacket($pk);*/ $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::PLAYER_SPAWN; $this->dataPacket($pk); $this->noDamageTicks = 60; foreach($this->usedChunks as $index => $c){ Level::getXZ($index, $chunkX, $chunkZ); foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){ if($entity !== $this and !$entity->closed and $entity->isAlive()){ $entity->spawnTo($this); } } } $this->teleport($pos); $this->allowFlight = (($this->gamemode == 3) or ($this->gamemode == 1)); $this->setHealth($this->getHealth()); $this->server->getPluginManager()->callEvent($ev = new PlayerJoinEvent($this, new TranslationContainer(TextFormat::YELLOW . "%multiplayer.player.joined", [ $this->getDisplayName() ]))); $this->sendSettings(); if(strlen(trim($msg = $ev->getJoinMessage())) > 0){ if($this->server->playerMsgType === Server:: PLAYER_MSG_TYPE_MESSAGE) $this->server->broadcastMessage($msg); elseif($this->server->playerMsgType === Server::PLAYER_MSG_TYPE_TIP) $this->server->broadcastTip(str_replace("@player", $this->getName(), $this->server->playerLoginMsg)); elseif($this->server->playerMsgType === Server::PLAYER_MSG_TYPE_POPUP) $this->server->broadcastPopup(str_replace("@player", $this->getName(), $this->server->playerLoginMsg)); } $this->server->onPlayerLogin($this); $this->spawnToAll(); $this->level->getWeather()->sendWeather($this); if($this->server->dserverConfig["enable"] and $this->server->dserverConfig["queryAutoUpdate"]){ $this->server->updateQuery(); } /*if($this->server->getUpdater()->hasUpdate() and $this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){ $this->server->getUpdater()->showPlayerUpdate($this); }*/ if($this->getHealth() <= 0){ $pk = new RespawnPacket(); $pos = $this->getSpawn(); $pk->x = $pos->x; $pk->y = $pos->y; $pk->z = $pos->z; $this->dataPacket($pk); } $this->inventory->sendContents($this); $this->inventory->sendArmorContents($this); } /** * @return bool */ protected function orderChunks(){ if($this->connected === false or $this->viewDistance === -1){ return false; } Timings::$playerChunkOrderTimer->startTiming(); $this->nextChunkOrderRun = 200; $radius = $this->server->getAllowedViewDistance($this->viewDistance); $radiusSquared = $radius ** 2; $newOrder = []; $unloadChunks = $this->usedChunks; $centerX = $this->x >> 4; $centerZ = $this->z >> 4; for($x = 0; $x < $radius; ++$x){ for($z = 0; $z <= $x; ++$z){ if(($x ** 2 + $z ** 2) > $radiusSquared){ break; //skip to next band } //If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be. /* Top right quadrant */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $x, $centerZ + $z)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); /* Top left quadrant */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $x - 1, $centerZ + $z)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); /* Bottom right quadrant */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $x, $centerZ - $z - 1)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); /* Bottom left quadrant */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $x - 1, $centerZ - $z - 1)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); if($x !== $z){ /* Top right quadrant mirror */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $z, $centerZ + $x)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); /* Top left quadrant mirror */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $z - 1, $centerZ + $x)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); /* Bottom right quadrant mirror */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $z, $centerZ - $x - 1)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); /* Bottom left quadrant mirror */ if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $z - 1, $centerZ - $x - 1)]) or $this->usedChunks[$index] === false){ $newOrder[$index] = true; } unset($unloadChunks[$index]); } } } foreach($unloadChunks as $index => $bool){ Level::getXZ($index, $X, $Z); $this->unloadChunk($X, $Z); } $this->loadQueue = $newOrder; Timings::$playerChunkOrderTimer->stopTiming(); return true; } /** * Batch a Data packet into the channel list to send at the end of the tick * * @param DataPacket $packet * * @return bool */ public function batchDataPacket(DataPacket $packet){ if($this->connected === false){ return false; } $timings = Timings::getSendDataPacketTimings($packet); $timings->startTiming(); $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet)); if($ev->isCancelled()){ $timings->stopTiming(); return false; } if(!isset($this->batchedPackets)){ $this->batchedPackets = []; } $this->batchedPackets[] = clone $packet; $timings->stopTiming(); return true; } /** * Sends an ordered DataPacket to the send buffer * * @param DataPacket $packet * @param bool $needACK * * @return int|bool */ public function dataPacket(DataPacket $packet, $needACK = false){ if(!$this->connected){ return false; } $timings = Timings::getSendDataPacketTimings($packet); $timings->startTiming(); $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet)); if($ev->isCancelled()){ $timings->stopTiming(); return false; } $identifier = $this->interface->putPacket($this, $packet, $needACK, false); if($needACK and $identifier !== null){ $this->needACK[$identifier] = false; $timings->stopTiming(); return $identifier; } $timings->stopTiming(); return true; } /** * @param DataPacket $packet * @param bool $needACK * * @return bool|int */ public function directDataPacket(DataPacket $packet, $needACK = false){ if($this->connected === false){ return false; } $timings = Timings::getSendDataPacketTimings($packet); $timings->startTiming(); $this->server->getPluginManager()->callEvent($ev = new DataPacketSendEvent($this, $packet)); if($ev->isCancelled()){ $timings->stopTiming(); return false; } $identifier = $this->interface->putPacket($this, $packet, $needACK, true); if($needACK and $identifier !== null){ $this->needACK[$identifier] = false; $timings->stopTiming(); return $identifier; } $timings->stopTiming(); return true; } /** * @param Vector3 $pos * * @return boolean */ public function sleepOn(Vector3 $pos){ if(!$this->isOnline()){ return false; } foreach($this->level->getNearbyEntities($this->boundingBox->grow(2, 1, 2), $this) as $p){ if($p instanceof Player){ if($p->sleeping !== null and $pos->distance($p->sleeping) <= 0.1){ return false; } } } $this->server->getPluginManager()->callEvent($ev = new PlayerBedEnterEvent($this, $this->level->getBlock($pos))); if($ev->isCancelled()){ return false; } $this->sleeping = clone $pos; $this->setDataProperty(self::DATA_PLAYER_BED_POSITION, self::DATA_TYPE_POS, [$pos->x, $pos->y, $pos->z]); $this->setDataFlag(self::DATA_PLAYER_FLAGS, self::DATA_PLAYER_FLAG_SLEEP, true, self::DATA_TYPE_BYTE); $this->setSpawn($pos); $this->level->sleepTicks = 60; return true; } /** * Sets the spawnpoint of the player (and the compass direction) to a Vector3, or set it on another world with a * Position object * * @param Vector3|Position $pos */ public function setSpawn(Vector3 $pos){ if(!($pos instanceof Position)){ $level = $this->level; }else{ $level = $pos->getLevel(); } $this->spawnPosition = new WeakPosition($pos->x, $pos->y, $pos->z, $level); $pk = new SetSpawnPositionPacket(); $pk->x = (int) $this->spawnPosition->x; $pk->y = (int) $this->spawnPosition->y; $pk->z = (int) $this->spawnPosition->z; $this->dataPacket($pk); } public function stopSleep(){ if($this->sleeping instanceof Vector3){ $this->server->getPluginManager()->callEvent($ev = new PlayerBedLeaveEvent($this, $this->level->getBlock($this->sleeping))); $this->sleeping = null; $this->setDataProperty(self::DATA_PLAYER_BED_POSITION, self::DATA_TYPE_POS, [0, 0, 0]); $this->setDataFlag(self::DATA_PLAYER_FLAGS, self::DATA_PLAYER_FLAG_SLEEP, false, self::DATA_TYPE_BYTE); $this->level->sleepTicks = 0; $pk = new AnimatePacket(); $pk->eid = $this->id; $pk->action = PlayerAnimationEvent::WAKE_UP; $this->dataPacket($pk); } } /** * @param string $achievementId * * @return bool */ public function awardAchievement($achievementId){ if(isset(Achievement::$list[$achievementId]) and !$this->hasAchievement($achievementId)){ foreach(Achievement::$list[$achievementId]["requires"] as $requirementId){ if(!$this->hasAchievement($requirementId)){ return false; } } $this->server->getPluginManager()->callEvent($ev = new PlayerAchievementAwardedEvent($this, $achievementId)); if(!$ev->isCancelled()){ $this->achievements[$achievementId] = true; Achievement::broadcast($this, $achievementId); return true; }else{ return false; } } return false; } /** * @return int */ public function getGamemode() : int{ return $this->gamemode; } /** * @internal * * Returns a client-friendly gamemode of the specified real gamemode * This function takes care of handling gamemodes known to MCPE (as of 1.1.0.3, that includes Survival, Creative and Adventure) * * TODO: remove this when Spectator Mode gets added properly to MCPE * * @param int $gamemode * * @return int */ public static function getClientFriendlyGamemode(int $gamemode) : int{ $gamemode &= 0x03; if($gamemode === Player::SPECTATOR){ return Player::CREATIVE; } return $gamemode; } /** * Sets the gamemode, and if needed, kicks the Player. * * @param int $gm * @param bool $client if the client made this change in their GUI * * @return bool */ public function setGamemode(int $gm, bool $client = false){ if($gm < 0 or $gm > 3 or $this->gamemode === $gm){ return false; } $this->server->getPluginManager()->callEvent($ev = new PlayerGameModeChangeEvent($this, $gm)); if($ev->isCancelled()){ if($client){ //gamemode change by client in the GUI $this->sendGamemode(); } return false; } if($this->server->autoClearInv){ $this->inventory->clearAll(); } $this->gamemode = $gm; $this->allowFlight = $this->isCreative(); if($this->isSpectator()){ $this->flying = true; $this->despawnFromAll(); // Client automatically turns off flight controls when on the ground. // A combination of this hack and a new AdventureSettings flag FINALLY // fixes spectator flight controls. Thank @robske110 for this hack. $this->teleport($this->temporalVector->setComponents($this->x, $this->y + 0.1, $this->z)); }else{ if($this->isSurvival()){ $this->flying = false; } $this->spawnToAll(); } $this->resetFallDistance(); $this->namedtag->playerGameType = new IntTag("playerGameType", $this->gamemode); if(!$client){ //Gamemode changed by server, do not send for client changes $this->sendGamemode(); }else{ Command::broadcastCommandMessage($this, new TranslationContainer("commands.gamemode.success.self", [Server::getGamemodeString($gm)])); } if($this->gamemode === Player::SPECTATOR){ $pk = new ContainerSetContentPacket(); $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; $this->dataPacket($pk); }else{ $pk = new ContainerSetContentPacket(); $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; $pk->slots = array_merge(Item::getCreativeItems(), $this->personalCreativeItems); $this->dataPacket($pk); } $this->sendSettings(); $this->inventory->sendContents($this); $this->inventory->sendContents($this->getViewers()); $this->inventory->sendHeldItem($this->hasSpawned); return true; } /** * @internal * Sends the player's gamemode to the client. */ public function sendGamemode(){ $pk = new SetPlayerGameTypePacket(); $pk->gamemode = Player::getClientFriendlyGamemode($this->gamemode); $this->dataPacket($pk); } /** * Sends all the option flags */ public function sendSettings(){ $pk = new AdventureSettingsPacket(); $pk->flags = 0; $pk->worldImmutable = $this->isAdventure(); $pk->autoJump = $this->autoJump; $pk->allowFlight = $this->allowFlight; $pk->noClip = $this->isSpectator(); $pk->worldBuilder = !($this->isAdventure()); $pk->isFlying = $this->flying; $pk->userPermission = ($this->isOp() ? AdventureSettingsPacket::PERMISSION_OPERATOR : AdventureSettingsPacket::PERMISSION_NORMAL); $this->dataPacket($pk); } /** * @return bool */ public function isSurvival() : bool{ return ($this->gamemode & 0x01) === 0; } /** * @return bool */ public function isCreative() : bool{ return ($this->gamemode & 0x01) > 0; } /** * @return bool */ public function isSpectator() : bool{ return $this->gamemode === 3; } /** * @return bool */ public function isAdventure() : bool{ return ($this->gamemode & 0x02) > 0; } /** * @return bool */ public function isFireProof() : bool{ return $this->isCreative(); } /** * @return array */ public function getDrops(){ if(!$this->isCreative()){ return parent::getDrops(); } return []; } /** * @param int $id * @param int $type * @param mixed $value * * @return bool */ public function setDataProperty($id, $type, $value){ if(parent::setDataProperty($id, $type, $value)){ $this->sendData($this, [$id => $this->dataProperties[$id]]); return true; } return false; } /** * @param $movX * @param $movY * @param $movZ * @param $dx * @param $dy * @param $dz */ protected function checkGroundState($movX, $movY, $movZ, $dx, $dy, $dz){ if(!$this->onGround or $movY != 0){ $bb = clone $this->boundingBox; $bb->maxY = $bb->minY + 0.5; $bb->minY -= 1; if(count($this->level->getCollisionBlocks($bb, true)) > 0){ $this->onGround = true; }else{ $this->onGround = false; } } $this->isCollided = $this->onGround; } protected function checkBlockCollision(){ foreach($blocksaround = $this->getBlocksAround() as $block){ $block->onEntityCollide($this); if($this->getServer()->redstoneEnabled){ if($block instanceof PressurePlate){ $this->activatedPressurePlates[Level::blockHash($block->x, $block->y, $block->z)] = $block; } } } if($this->getServer()->redstoneEnabled){ /** @var \pocketmine\block\PressurePlate $block * */ foreach($this->activatedPressurePlates as $key => $block){ if(!isset($blocksaround[$key])) $block->checkActivation(); } } } /** * @param $tickDiff */ protected function checkNearEntities($tickDiff){ foreach($this->level->getNearbyEntities($this->boundingBox->grow(0.5, 0.5, 0.5), $this) as $entity){ $entity->scheduleUpdate(); if(!$entity->isAlive()){ continue; } if($entity instanceof Arrow and $entity->hadCollision){ $item = Item::get(Item::ARROW, $entity->getPotionId(), 1); $add = false; if(!$this->server->allowInventoryCheats and !$this->isCreative()){ if(!$this->getFloatingInventory()->canAddItem($item) or !$this->inventory->canAddItem($item)){ //The item is added to the floating inventory to allow client to handle the pickup //We have to also check if it can be added to the real inventory before sending packets. continue; } $add = true; } $this->server->getPluginManager()->callEvent($ev = new InventoryPickupArrowEvent($this->inventory, $entity)); if($ev->isCancelled()){ continue; } $pk = new TakeItemEntityPacket(); $pk->eid = $this->id; $pk->target = $entity->getId(); $this->server->broadcastPacket($entity->getViewers(), $pk); if($add){ $this->getFloatingInventory()->addItem(clone $item); } $entity->kill(); }elseif($entity instanceof DroppedItem){ if($entity->getPickupDelay() <= 0){ $item = $entity->getItem(); if($item instanceof Item){ $add = false; if(!$this->server->allowInventoryCheats and !$this->isCreative()){ if(!$this->getFloatingInventory()->canAddItem($item) or !$this->inventory->canAddItem($item)){ continue; } $add = true; } $this->server->getPluginManager()->callEvent($ev = new InventoryPickupItemEvent($this->inventory, $entity)); if($ev->isCancelled()){ continue; } switch($item->getId()){ case Item::WOOD: $this->awardAchievement("mineWood"); break; case Item::DIAMOND: $this->awardAchievement("diamond"); break; } $pk = new TakeItemEntityPacket(); $pk->eid = $this->id; $pk->target = $entity->getId(); $this->server->broadcastPacket($entity->getViewers(), $pk); if($add){ $this->getFloatingInventory()->addItem(clone $item); } $entity->kill(); } } } } } /** * @param $tickDiff */ protected function processMovement($tickDiff){ if(!$this->isAlive() or !$this->spawned or $this->newPosition === null or $this->teleportPosition !== null or $this->isSleeping()){ return; } $newPos = $this->newPosition; $distanceSquared = $newPos->distanceSquared($this); $revert = false; if(($distanceSquared / ($tickDiff ** 2)) > 100 and !$this->allowMovementCheats){ $this->server->getLogger()->warning($this->getName() . " moved too fast, reverting movement"); $revert = true; }else{ if($this->chunk === null or !$this->chunk->isGenerated()){ $chunk = $this->level->getChunk($newPos->x >> 4, $newPos->z >> 4, false); if($chunk === null or !$chunk->isGenerated()){ $revert = true; $this->nextChunkOrderRun = 0; }else{ if($this->chunk !== null){ $this->chunk->removeEntity($this); } $this->chunk = $chunk; } } } if(!$revert and $distanceSquared != 0){ $dx = $newPos->x - $this->x; $dy = $newPos->y - $this->y; $dz = $newPos->z - $this->z; $this->move($dx, $dy, $dz); $diffX = $this->x - $newPos->x; $diffY = $this->y - $newPos->y; $diffZ = $this->z - $newPos->z; $diff = ($diffX ** 2 + $diffY ** 2 + $diffZ ** 2) / ($tickDiff ** 2); if($this->isSurvival() and !$revert and $diff > 0.0625){ $ev = new PlayerIllegalMoveEvent($this, $newPos); $ev->setCancelled($this->allowMovementCheats); $this->server->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $revert = true; $this->server->getLogger()->warning($this->getServer()->getLanguage()->translateString("pocketmine.player.invalidMove", [$this->getName()])); } } if($diff > 0){ $this->x = $newPos->x; $this->y = $newPos->y; $this->z = $newPos->z; $radius = $this->width / 2; $this->boundingBox->setBounds($this->x - $radius, $this->y, $this->z - $radius, $this->x + $radius, $this->y + $this->height, $this->z + $radius); } } $from = new Location($this->lastX, $this->lastY, $this->lastZ, $this->lastYaw, $this->lastPitch, $this->level); $to = $this->getLocation(); $delta = pow($this->lastX - $to->x, 2) + pow($this->lastY - $to->y, 2) + pow($this->lastZ - $to->z, 2); $deltaAngle = abs($this->lastYaw - $to->yaw) + abs($this->lastPitch - $to->pitch); if(!$revert and ($delta > 0.0001 or $deltaAngle > 1.0)){ $isFirst = ($this->lastX === null or $this->lastY === null or $this->lastZ === null); $this->lastX = $to->x; $this->lastY = $to->y; $this->lastZ = $to->z; $this->lastYaw = $to->yaw; $this->lastPitch = $to->pitch; if(!$isFirst){ $ev = new PlayerMoveEvent($this, $from, $to); $this->setMoving(true); $this->server->getPluginManager()->callEvent($ev); if(!($revert = $ev->isCancelled())){ //Yes, this is intended if($this->server->netherEnabled){ if($this->isInsideOfPortal()){ if($this->portalTime == 0){ $this->portalTime = $this->server->getTick(); } }else{ $this->portalTime = 0; } } if($to->distanceSquared($ev->getTo()) > 0.01){ //If plugins modify the destination $this->teleport($ev->getTo()); }else{ $this->level->addEntityMovement($this->x >> 4, $this->z >> 4, $this->getId(), $this->x, $this->y + $this->getEyeHeight(), $this->z, $this->yaw, $this->pitch, $this->yaw); } if($this->fishingHook instanceof FishingHook){ if($this->distance($this->fishingHook) > 33 or $this->inventory->getItemInHand()->getId() !== Item::FISHING_ROD){ $this->setFishingHook(); } } } } if(!$this->isSpectator()){ $this->checkNearEntities($tickDiff); } $this->speed = ($to->subtract($from))->divide($tickDiff); }elseif($distanceSquared == 0){ $this->speed = new Vector3(0, 0, 0); $this->setMoving(false); } if($revert){ $this->lastX = $from->x; $this->lastY = $from->y; $this->lastZ = $from->z; $this->lastYaw = $from->yaw; $this->lastPitch = $from->pitch; $this->sendPosition($from, $from->yaw, $from->pitch, MovePlayerPacket::MODE_RESET); $this->forceMovement = new Vector3($from->x, $from->y, $from->z); }else{ $this->forceMovement = null; if($distanceSquared != 0 and $this->nextChunkOrderRun > 20){ $this->nextChunkOrderRun = 20; } } $this->newPosition = null; } /** * @param Vector3 $mot * * @return bool */ public function setMotion(Vector3 $mot){ if(parent::setMotion($mot)){ if($this->chunk !== null){ $this->level->addEntityMotion($this->chunk->getX(), $this->chunk->getZ(), $this->getId(), $this->motionX, $this->motionY, $this->motionZ); $pk = new SetEntityMotionPacket(); $pk->eid = $this->id; $pk->motionX = $mot->x; $pk->motionY = $mot->y; $pk->motionZ = $mot->z; $this->dataPacket($pk); } if($this->motionY > 0){ $this->startAirTicks = (-(log($this->gravity / ($this->gravity + $this->drag * $this->motionY))) / $this->drag) * 2 + 5; } return true; } return false; } protected function updateMovement(){ } public $foodTick = 0; public $starvationTick = 0; public $foodUsageTime = 0; protected $moving = false; /** * @param $moving */ public function setMoving($moving){ $this->moving = $moving; } /** * @return bool */ public function isMoving() : bool{ return $this->moving; } /** * @param bool $sendAll */ public function sendAttributes(bool $sendAll = false){ $entries = $sendAll ? $this->attributeMap->getAll() : $this->attributeMap->needSend(); if(count($entries) > 0){ $pk = new UpdateAttributesPacket(); $pk->entityId = $this->id; $pk->entries = $entries; $this->dataPacket($pk); foreach($entries as $entry){ $entry->markSynchronized(); } } } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if(!$this->loggedIn){ return false; } $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0){ return true; } $this->messageCounter = 2; $this->lastUpdate = $currentTick; $this->sendAttributes(); if(!$this->isAlive() and $this->spawned){ ++$this->deadTicks; if($this->deadTicks >= 10){ $this->despawnFromAll(); } return true; } $this->timings->startTiming(); if($this->spawned){ if($this->server->netherEnabled){ if(($this->isCreative() or $this->isSurvival() and $this->server->getTick() - $this->portalTime >= 80) and $this->portalTime > 0){ $netherLevel = null; if($this->server->isLevelLoaded($this->server->netherName) or $this->server->loadLevel($this->server->netherName)){ $netherLevel = $this->server->getLevelByName($this->server->netherName); } if($netherLevel instanceof Level){ if($this->getLevel() !== $netherLevel){ $this->fromPos = $this->getPosition(); $this->fromPos->x = ((int) $this->fromPos->x) + 0.5; $this->fromPos->z = ((int) $this->fromPos->z) + 0.5; $this->teleport($this->shouldResPos = $netherLevel->getSafeSpawn()); }elseif($this->fromPos instanceof Position){ if(!($this->getLevel()->isChunkLoaded($this->fromPos->x, $this->fromPos->z))){ $this->getLevel()->loadChunk($this->fromPos->x, $this->fromPos->z); } $add = [1, 0, -1, 0, 0, 1, 0, -1]; $tempos = null; for($j = 2; $j < 5; $j++){ for($i = 0; $i < 4; $i++){ if($this->fromPos->getLevel()->getBlock($this->temporalVector->fromObjectAdd($this->fromPos, $add[$i] * $j, 0, $add[$i + 4] * $j))->getId() === Block::AIR){ if($this->fromPos->getLevel()->getBlock($this->temporalVector->fromObjectAdd($this->fromPos, $add[$i] * $j, 1, $add[$i + 4] * $j))->getId() === Block::AIR){ $tempos = $this->fromPos->add($add[$i] * $j, 0, $add[$i + 4] * $j); //$this->getLevel()->getServer()->getLogger()->debug($tempos); break; } } } if($tempos != null){ break; } } if($tempos === null){ $tempos = $this->fromPos->add(mt_rand(-2, 2), 0, mt_rand(-2, 2)); } $this->teleport($this->shouldResPos = $tempos); $add = null; $tempos = null; $this->fromPos = null; }else{ $this->teleport($this->shouldResPos = $this->server->getDefaultLevel()->getSafeSpawn()); } $this->portalTime = 0; } } } $this->processMovement($tickDiff); $this->entityBaseTick($tickDiff); if($this->isOnFire() or $this->lastUpdate % 10 == 0){ if($this->isCreative() and !$this->isInsideOfFire()){ $this->extinguish(); }elseif($this->getLevel()->getWeather()->isRainy()){ if($this->getLevel()->canBlockSeeSky($this)){ $this->extinguish(); } } } if(!$this->isSpectator() and $this->speed !== null){ if($this->hasEffect(Effect::LEVITATION)){ $this->inAirTicks = 0; } if($this->onGround){ if($this->inAirTicks !== 0){ $this->startAirTicks = 5; } $this->inAirTicks = 0; }else{ if($this->getInventory()->getItem($this->getInventory()->getSize() + 1)->getId() == "444"){ #enable use of elytra. todo: check if it is open $this->inAirTicks = 0; } if(!$this->allowFlight and $this->inAirTicks > 10 and !$this->isSleeping() and !$this->isImmobile()){ $expectedVelocity = (-$this->gravity) / $this->drag - ((-$this->gravity) / $this->drag) * exp(-$this->drag * ($this->inAirTicks - $this->startAirTicks)); $diff = ($this->speed->y - $expectedVelocity) ** 2; if(!$this->hasEffect(Effect::JUMP) and $diff > 0.6 and $expectedVelocity < $this->speed->y and !$this->server->getAllowFlight()){ if($this->inAirTicks < 1000){ $this->setMotion(new Vector3(0, $expectedVelocity, 0)); }elseif($this->kick("Flying is not enabled on this server", false)){ $this->timings->stopTiming(); return false; } } } ++$this->inAirTicks; } } if($this->getTransactionQueue() !== null){ $this->getTransactionQueue()->execute(); } } $this->checkTeleportPosition(); $this->timings->stopTiming(); if(count($this->messageQueue) > 0){ $pk = new TextPacket(); $pk->type = TextPacket::TYPE_RAW; $pk->message = implode("\n", $this->messageQueue); $this->dataPacket($pk); $this->messageQueue = []; } return true; } public function checkNetwork(){ if(!$this->isOnline()){ return; } if($this->nextChunkOrderRun-- <= 0 or $this->chunk === null){ $this->orderChunks(); } if(count($this->loadQueue) > 0 or !$this->spawned){ $this->sendNextChunk(); } if(count($this->batchedPackets) > 0){ $this->server->batchPackets([$this], $this->batchedPackets, false); $this->batchedPackets = []; } } /** * @param Vector3 $pos * @param $maxDistance * @param float $maxDiff * * @return bool */ public function canInteract(Vector3 $pos, $maxDistance, $maxDiff = 0.5){ $eyePos = $this->getPosition()->add(0, $this->getEyeHeight(), 0); if($eyePos->distanceSquared($pos) > $maxDistance ** 2){ return false; } $dV = $this->getDirectionPlane(); $dot = $dV->dot(new Vector2($eyePos->x, $eyePos->z)); $dot1 = $dV->dot(new Vector2($pos->x, $pos->z)); return ($dot1 - $dot) >= -$maxDiff; } public function onPlayerPreLogin(){ $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_SUCCESS; $this->dataPacket($pk); $this->processLogin(); } public function clearCreativeItems(){ $this->personalCreativeItems = []; } /** * @return array */ public function getCreativeItems() : array{ return $this->personalCreativeItems; } /** * @param Item $item */ public function addCreativeItem(Item $item){ $this->personalCreativeItems[] = Item::get($item->getId(), $item->getDamage()); } /** * @param Item $item */ public function removeCreativeItem(Item $item){ $index = $this->getCreativeItemIndex($item); if($index !== -1){ unset($this->personalCreativeItems[$index]); } } /** * @param Item $item * * @return int */ public function getCreativeItemIndex(Item $item) : int{ foreach($this->personalCreativeItems as $i => $d){ if($item->equals($d, !$item->isTool())){ return $i; } } return -1; } protected function processLogin(){ if(!$this->server->isWhitelisted(strtolower($this->getName()))){ $this->close($this->getLeaveMessage(), "Server is white-listed"); return; }elseif($this->server->getNameBans()->isBanned(strtolower($this->getName())) or $this->server->getIPBans()->isBanned($this->getAddress()) or $this->server->getCIDBans()->isBanned($this->randomClientId)){ $this->close($this->getLeaveMessage(), TextFormat::RED . "You are banned"); return; } if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){ $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this); } if($this->hasPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE)){ $this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); } foreach($this->server->getOnlinePlayers() as $p){ if($p !== $this and strtolower($p->getName()) === strtolower($this->getName())){ if($p->kick("logged in from another location") === false){ $this->close($this->getLeaveMessage(), "Logged in from another location"); return; } }elseif($p->loggedIn and $this->getUniqueId()->equals($p->getUniqueId())){ if($p->kick("logged in from another location") === false){ $this->close($this->getLeaveMessage(), "Logged in from another location"); return; } } } $this->setNameTag($this->getDisplayName()); $nbt = $this->server->getOfflinePlayerData($this->username); $this->playedBefore = ($nbt["lastPlayed"] - $nbt["firstPlayed"]) > 1; if(!isset($nbt->NameTag)){ $nbt->NameTag = new StringTag("NameTag", $this->username); }else{ $nbt["NameTag"] = $this->username; } $this->gamemode = $nbt["playerGameType"] & 0x03; if($this->server->getForceGamemode()){ $this->gamemode = $this->server->getGamemode(); $nbt->playerGameType = new IntTag("playerGameType", $this->gamemode); } $this->allowFlight = $this->isCreative(); if(($level = $this->server->getLevelByName($nbt["Level"])) === null){ $this->setLevel($this->server->getDefaultLevel()); $nbt["Level"] = $this->level->getName(); $nbt["Pos"][0] = $this->level->getSpawnLocation()->x; $nbt["Pos"][1] = $this->level->getSpawnLocation()->y; $nbt["Pos"][2] = $this->level->getSpawnLocation()->z; }else{ $this->setLevel($level); } if(!($nbt instanceof CompoundTag)){ $this->close($this->getLeaveMessage(), "Invalid data"); return; } $this->achievements = []; /** @var ByteTag $achievement */ foreach($nbt->Achievements as $achievement){ $this->achievements[$achievement->getName()] = $achievement->getValue() > 0 ? true : false; } $nbt->lastPlayed = new LongTag("lastPlayed", floor(microtime(true) * 1000)); if($this->server->getAutoSave()){ $this->server->saveOfflinePlayerData($this->username, $nbt, true); } parent::__construct($this->level, $nbt); $this->loggedIn = true; $this->server->addOnlinePlayer($this); if(!$this->isConnected()){ return; } $this->dataPacket(new ResourcePacksInfoPacket()); if(!$this->hasValidSpawnPosition() and isset($this->namedtag->SpawnLevel) and ($level = $this->server->getLevelByName($this->namedtag["SpawnLevel"])) instanceof Level){ $this->spawnPosition = new WeakPosition($this->namedtag["SpawnX"], $this->namedtag["SpawnY"], $this->namedtag["SpawnZ"], $level); } $spawnPosition = $this->getSpawn(); $pk = new StartGamePacket(); $pk->entityUniqueId = $this->id; $pk->entityRuntimeId = $this->id; $pk->playerGamemode = Player::getClientFriendlyGamemode($this->gamemode); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->pitch = $this->pitch; $pk->yaw = $this->yaw; $pk->seed = -1; $pk->dimension = $this->level->getDimension(); $pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode()); $pk->difficulty = $this->server->getDifficulty(); $pk->spawnX = $spawnPosition->getFloorX(); $pk->spawnY = $spawnPosition->getFloorY(); $pk->spawnZ = $spawnPosition->getFloorZ(); $pk->hasAchievementsDisabled = 1; $pk->dayCycleStopTime = -1; //TODO: implement this properly $pk->eduMode = 0; $pk->rainLevel = 0; //TODO: implement these properly $pk->lightningLevel = 0; $pk->commandsEnabled = 1; $pk->levelId = ""; $pk->worldName = $this->server->getMotd(); $this->dataPacket($pk); $this->server->getPluginManager()->callEvent($ev = new PlayerLoginEvent($this, "Plugin reason")); if($ev->isCancelled()){ $this->close($this->getLeaveMessage(), $ev->getKickMessage()); return; } $pk = new SetTimePacket(); $pk->time = $this->level->getTime(); $pk->started = $this->level->stopTime == false; $this->dataPacket($pk); $this->sendAttributes(true); $this->setNameTagVisible(true); $this->setNameTagAlwaysVisible(true); $this->setCanClimb(true); $this->server->getLogger()->info($this->getServer()->getLanguage()->translateString("pocketmine.player.logIn", [ TextFormat::AQUA . $this->username . TextFormat::WHITE, $this->ip, $this->port, TextFormat::GREEN . $this->randomClientId . TextFormat::WHITE, $this->id, $this->level->getName(), round($this->x, 4), round($this->y, 4), round($this->z, 4) ])); /*if($this->isOp()){ $this->setRemoveFormat(false); }*/ if($this->gamemode === Player::SPECTATOR){ $pk = new ContainerSetContentPacket(); $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; $this->dataPacket($pk); }else{ $pk = new ContainerSetContentPacket(); $pk->windowid = ContainerSetContentPacket::SPECIAL_CREATIVE; $pk->slots = array_merge(Item::getCreativeItems(), $this->personalCreativeItems); $this->dataPacket($pk); } $this->sendCommandData(); $this->level->getWeather()->sendWeather($this); $this->forceMovement = $this->teleportPosition = $this->getPosition(); } /** * @return mixed */ public function getProtocol(){ return $this->protocol; } /** * Handles a Minecraft packet * TODO: Separate all of this in handlers * * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. * * @param DataPacket $packet */ public function handleDataPacket(DataPacket $packet){ if($this->connected === false){ return; } if($packet::NETWORK_ID === 0xfe){ /** @var BatchPacket $packet */ $this->server->getNetwork()->processBatch($packet, $this); return; } $timings = Timings::getReceiveDataPacketTimings($packet); $timings->startTiming(); $this->server->getPluginManager()->callEvent($ev = new DataPacketReceiveEvent($this, $packet)); if($ev->isCancelled()){ $timings->stopTiming(); return; } switch($packet::NETWORK_ID){ case ProtocolInfo::LEVEL_SOUND_EVENT_PACKET: $this->level->addChunkPacket($packet->x >> 4, $packet->z >> 4, $packet); break; case ProtocolInfo::PLAYER_INPUT_PACKET: break; case ProtocolInfo::LOGIN_PACKET: if($this->loggedIn){ break; } $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_SUCCESS; $this->dataPacket($pk); $this->username = TextFormat::clean($packet->username); $this->displayName = $this->username; $this->setNameTag($this->username); $this->iusername = strtolower($this->username); $this->protocol = $packet->protocol; $this->deviceModel = $packet->deviceModel; $this->deviceOS = $packet->deviceOS; if($this->server->getConfigBoolean("online-mode", false) && $packet->identityPublicKey === null){ $this->kick("disconnectionScreen.notAuthenticated", false); break; } if(count($this->server->getOnlinePlayers()) >= $this->server->getMaxPlayers() and $this->kick("disconnectionScreen.serverFull", false)){ break; } if(!in_array($packet->protocol, ProtocolInfo::ACCEPTED_PROTOCOLS)){ if($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL){ $message = "disconnectionScreen.outdatedClient"; $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_FAILED_CLIENT; $this->directDataPacket($pk); }else{ $message = "disconnectionScreen.outdatedServer"; $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_FAILED_SERVER; $this->directDataPacket($pk); } $this->close("", $message, false); break; } $this->randomClientId = $packet->clientId; $this->uuid = UUID::fromString($packet->clientUUID); $this->rawUUID = $this->uuid->toBinary(); $valid = true; $len = strlen($packet->username); if($len > 16 or $len < 3){ $valid = false; } for($i = 0; $i < $len and $valid; ++$i){ $c = ord($packet->username{$i}); if(($c >= ord("a") and $c <= ord("z")) or ($c >= ord("A") and $c <= ord("Z")) or ($c >= ord("0") and $c <= ord("9")) or $c === ord("_")){ continue; } $valid = false; break; } if(!$valid or $this->iusername === "rcon" or $this->iusername === "console"){ $this->close("", "disconnectionScreen.invalidName"); break; } if((strlen($packet->skin) != 64 * 64 * 4) and (strlen($packet->skin) != 64 * 32 * 4)){ $this->close("", "disconnectionScreen.invalidSkin"); break; } $this->setSkin($packet->skin, $packet->skinId); $this->server->getPluginManager()->callEvent($ev = new PlayerPreLoginEvent($this, "Plugin reason")); if($ev->isCancelled()){ $this->close("", $ev->getKickMessage()); break; } $pk = new PlayStatusPacket(); $pk->status = PlayStatusPacket::LOGIN_SUCCESS; $this->directDataPacket($pk); $infoPacket = new ResourcePacksInfoPacket(); $infoPacket->resourcePackEntries = $this->server->getResourcePackManager()->getResourceStack(); $infoPacket->mustAccept = $this->server->getResourcePackManager()->resourcePacksRequired(); $this->directDataPacket($infoPacket); /*if($this->isConnected()){ $this->processLogin(); }*/ break; case ProtocolInfo::RESOURCE_PACK_CLIENT_RESPONSE_PACKET: switch($packet->status){ case ResourcePackClientResponsePacket::STATUS_REFUSED: //Client refused to download the required resource pack $this->close("", $this->server->getLanguage()->translateString("disconnectionScreen.refusedResourcePack"), true); break; case ResourcePackClientResponsePacket::STATUS_SEND_PACKS: $manager = $this->server->getResourcePackManager(); foreach($packet->packIds as $uuid){ $pack = $manager->getPackById($uuid); if(!($pack instanceof ResourcePack)){ //Client requested a resource pack but we don't have it available on the server $this->close("", $this->server->getLanguage()->translateString("disconnectionScreen.unavailableResourcePack"), true); break; } $pk = new ResourcePackDataInfoPacket(); $pk->packId = $pack->getPackId(); $pk->maxChunkSize = 1048576; //1MB $pk->chunkCount = $pack->getPackSize() / $pk->maxChunkSize; $pk->compressedPackSize = $pack->getPackSize(); $pk->sha256 = $pack->getSha256(); $this->dataPacket($pk); } break; case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS: $pk = new ResourcePackStackPacket(); $manager = $this->server->getResourcePackManager(); $pk->resourcePackStack = $manager->getResourceStack(); $pk->mustAccept = $manager->resourcePacksRequired(); $this->dataPacket($pk); break; case ResourcePackClientResponsePacket::STATUS_COMPLETED: $this->processLogin(); break; } break; case ProtocolInfo::RESOURCE_PACK_CHUNK_REQUEST_PACKET: $manager = $this->server->getResourcePackManager(); $pack = $manager->getPackById($packet->packId); if(!($pack instanceof ResourcePack)){ $this->close("", "disconnectionScreen.resourcePack", true); return true; } $pk = new ResourcePackChunkDataPacket(); $pk->packId = $pack->getPackId(); $pk->chunkIndex = $packet->chunkIndex; $pk->data = $pack->getPackChunk(1048576 * $packet->chunkIndex, 1048576); $pk->progress = (1048576 * $packet->chunkIndex); $this->dataPacket($pk); break; case ProtocolInfo::MOVE_PLAYER_PACKET: if($this->linkedEntity instanceof Entity){ $entity = $this->linkedEntity; if($entity instanceof Boat){ $entity->setPosition($this->temporalVector->setComponents($packet->x, $packet->y - 0.3, $packet->z)); } /*if($entity instanceof Minecart){ $entity->isFreeMoving = true; $entity->motionX = -sin($packet->yaw / 180 * M_PI); $entity->motionZ = cos($packet->yaw / 180 * M_PI); }*/ } $newPos = new Vector3($packet->x, $packet->y - $this->getEyeHeight(), $packet->z); if($newPos->distanceSquared($this) == 0 and ($packet->yaw % 360) === $this->yaw and ($packet->pitch % 360) === $this->pitch){ //player hasn't moved, just client spamming packets break; } $revert = false; if(!$this->isAlive() or $this->spawned !== true){ $revert = true; $this->forceMovement = new Vector3($this->x, $this->y, $this->z); } if($this->teleportPosition !== null or ($this->forceMovement instanceof Vector3 and ($newPos->distanceSquared($this->forceMovement) > 0.1 or $revert))){ $this->sendPosition($this->forceMovement, $packet->yaw, $packet->pitch, MovePlayerPacket::MODE_RESET); }else{ $packet->yaw %= 360; $packet->pitch %= 360; if($packet->yaw < 0){ $packet->yaw += 360; } $this->setRotation($packet->yaw, $packet->pitch); $this->newPosition = $newPos; $this->forceMovement = null; } break; case ProtocolInfo::ADVENTURE_SETTINGS_PACKET: //TODO: player abilities, check for other changes $isCheater = ($this->allowFlight === false && ($packet->flags >> 9) & 0x01 === 1) || (!$this->isSpectator() && ($packet->flags >> 7) & 0x01 === 1); if(($packet->isFlying and !$this->allowFlight and !$this->server->getAllowFlight()) or $isCheater){ $this->kick("Flying is not enabled on this server"); //TODO: Customizing the message break; }else{ $this->server->getPluginManager()->callEvent($ev = new PlayerToggleFlightEvent($this, $packet->isFlying)); if($ev->isCancelled()){ $this->sendSettings(); }else{ $this->flying = $ev->isFlying(); } break; } break; case ProtocolInfo::MOB_EQUIPMENT_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } /** * Handle hotbar slot remapping * This is the only time and place when hotbar mapping should ever be changed. * Changing hotbar slot mapping at will has been deprecated because it causes far too many * issues with Windows 10 Edition Beta. */ $this->inventory->setHeldItemIndex($packet->selectedSlot, false, $packet->slot); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); break; case ProtocolInfo::USE_ITEM_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $blockVector = new Vector3($packet->x, $packet->y, $packet->z); $this->craftingType = self::CRAFTING_SMALL; if($packet->face >= 0 and $packet->face <= 5){ //Use Block, place $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); if(!$this->canInteract($blockVector->add(0.5, 0.5, 0.5), 13) or $this->isSpectator()){ }elseif($this->isCreative()){ $item = $this->inventory->getItemInHand(); if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this) === true){ break; } }elseif(!$this->inventory->getItemInHand()->equals($packet->item)){ $this->inventory->sendHeldItem($this); }else{ $item = $this->inventory->getItemInHand(); $oldItem = clone $item; if($this->level->useItemOn($blockVector, $item, $packet->face, $packet->fx, $packet->fy, $packet->fz, $this)){ if(!$item->equals($oldItem) or $item->getCount() !== $oldItem->getCount()){ $this->inventory->setItemInHand($item); $this->inventory->sendHeldItem($this->hasSpawned); } break; } } $this->inventory->sendHeldItem($this); if($blockVector->distanceSquared($this) > 10000){ break; } $target = $this->level->getBlock($blockVector); $block = $target->getSide($packet->face); $this->level->sendBlocks([$this], [$target, $block], UpdateBlockPacket::FLAG_ALL_PRIORITY); break; }elseif($packet->face === -1){ $aimPos = (new Vector3($packet->x / 32768, $packet->y / 32768, $packet->z / 32768))->normalize(); if($this->isCreative()){ $item = $this->inventory->getItemInHand(); }elseif(!$this->inventory->getItemInHand()->equals($packet->item)){ $this->inventory->sendHeldItem($this); break; }else{ $item = $this->inventory->getItemInHand(); } $ev = new PlayerInteractEvent($this, $item, $aimPos, $packet->face, PlayerInteractEvent::RIGHT_CLICK_AIR); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->inventory->sendHeldItem($this); break; } $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x), new DoubleTag("", $this->y + $this->getEyeHeight()), new DoubleTag("", $this->z), ]), "Motion" => new ListTag("Motion", [ //TODO: remove this because of a broken client new DoubleTag("", -sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)), new DoubleTag("", -sin($this->pitch / 180 * M_PI)), new DoubleTag("", cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)), ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", $this->yaw), new FloatTag("", $this->pitch), ]), ]); $entity = null; $reduceCount = true; switch($item->getId()){ case Item::FISHING_ROD: $this->server->getPluginManager()->callEvent($ev = new PlayerUseFishingRodEvent($this, ($this->isFishing() ? PlayerUseFishingRodEvent::ACTION_STOP_FISHING : PlayerUseFishingRodEvent::ACTION_START_FISHING))); if(!$ev->isCancelled()){ if(!$this->isFishing()){ $f = 0.6; $entity = Entity::createEntity("FishingHook", $this->getLevel(), $nbt, $this); $entity->setMotion($entity->getMotion()->multiply($f)); } } $this->setFishingHook($entity); $reduceCount = false; break; case Item::SNOWBALL: $f = 1.5; $entity = Entity::createEntity("Snowball", $this->getLevel(), $nbt, $this); $entity->setMotion($entity->getMotion()->multiply($f)); $this->server->getPluginManager()->callEvent($ev = new ProjectileLaunchEvent($entity)); if($ev->isCancelled()){ $entity->kill(); } break; case Item::EGG: $f = 1.5; $entity = Entity::createEntity("Egg", $this->getLevel(), $nbt, $this); $entity->setMotion($entity->getMotion()->multiply($f)); $this->server->getPluginManager()->callEvent($ev = new ProjectileLaunchEvent($entity)); if($ev->isCancelled()){ $entity->kill(); } break; case Item::ENCHANTING_BOTTLE: $f = 1.1; $entity = Entity::createEntity("ThrownExpBottle", $this->getLevel(), $nbt, $this); $entity->setMotion($entity->getMotion()->multiply($f)); $this->server->getPluginManager()->callEvent($ev = new ProjectileLaunchEvent($entity)); if($ev->isCancelled()){ $entity->kill(); } break; case Item::SPLASH_POTION: if($this->server->allowSplashPotion){ $f = 1.1; $nbt["PotionId"] = new ShortTag("PotionId", $item->getDamage()); $entity = Entity::createEntity("ThrownPotion", $this->getLevel(), $nbt, $this); $entity->setMotion($entity->getMotion()->multiply($f)); $this->server->getPluginManager()->callEvent($ev = new ProjectileLaunchEvent($entity)); if($ev->isCancelled()){ $entity->kill(); } } break; case Item::ENDER_PEARL: if(floor(($time = microtime(true)) - $this->lastEnderPearlUse) >= 1){ $f = 1.1; $entity = Entity::createEntity("EnderPearl", $this->getLevel(), $nbt, $this); $entity->setMotion($entity->getMotion()->multiply($f)); $this->server->getPluginManager()->callEvent($ev = new ProjectileLaunchEvent($entity)); if($ev->isCancelled()){ $entity->kill(); }else{ $this->lastEnderPearlUse = $time; } } break; } if($entity instanceof Projectile and $entity->isAlive()){ if($reduceCount and $this->isSurvival()){ $item->setCount($item->getCount() - 1); $this->inventory->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } $entity->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, true); $this->startAction = $this->server->getTick(); } break; case ProtocolInfo::PLAYER_ACTION_PACKET: if($this->spawned === false or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_SPAWN_SAME_DIMENSION and $packet->action !== PlayerActionPacket::ACTION_SPAWN_OVERWORLD and $packet->action !== PlayerActionPacket::ACTION_SPAWN_NETHER)){ break; } $pos = new Vector3($packet->x, $packet->y, $packet->z); switch($packet->action){ case PlayerActionPacket::ACTION_START_BREAK: if($this->lastBreak !== PHP_INT_MAX or $pos->distanceSquared($this) > 10000){ break; } $target = $this->level->getBlock($pos); $ev = new PlayerInteractEvent($this, $this->inventory->getItemInHand(), $target, $packet->face, $target->getId() === 0 ? PlayerInteractEvent::LEFT_CLICK_AIR : PlayerInteractEvent::LEFT_CLICK_BLOCK); $this->getServer()->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $side = $target->getSide($packet->face); if($side instanceof Fire){ $side->getLevel()->setBlock($side, new Air()); break; } if(!$this->isCreative()){ $breakTime = ceil($target->getBreakTime($this->inventory->getItemInHand()) * 20); if($breakTime > 0){ $this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_BLOCK_START_BREAK, (int) (65535 / $breakTime)); } } $this->lastBreak = microtime(true); }else{ $this->inventory->sendHeldItem($this); } break; case PlayerActionPacket::ACTION_ABORT_BREAK: $this->lastBreak = PHP_INT_MAX; $this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_BLOCK_STOP_BREAK); break; case PlayerActionPacket::ACTION_STOP_BREAK: $this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_BLOCK_STOP_BREAK); break; case PlayerActionPacket::ACTION_RELEASE_ITEM: if($this->startAction > -1 and $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION)){ if($this->inventory->getItemInHand()->getId() === Item::BOW){ $bow = $this->inventory->getItemInHand(); if($this->isSurvival() and !$this->inventory->contains(Item::get(Item::ARROW, -1))){ $this->inventory->sendContents($this); break; } $arrow = null; $index = $this->inventory->first(Item::get(Item::ARROW, -1)); if($index !== -1){ $arrow = $this->inventory->getItem($index); $arrow->setCount(1); }elseif($this->isCreative()){ $arrow = Item::get(Item::ARROW, 0, 1); }else{ $this->inventory->sendContents($this); break; } $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x), new DoubleTag("", $this->y + $this->getEyeHeight()), new DoubleTag("", $this->z) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", -sin($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)), new DoubleTag("", -sin($this->pitch / 180 * M_PI)), new DoubleTag("", cos($this->yaw / 180 * M_PI) * cos($this->pitch / 180 * M_PI)) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", $this->yaw), new FloatTag("", $this->pitch) ]), "Fire" => new ShortTag("Fire", $this->isOnFire() ? 45 * 60 : 0), "Potion" => new ShortTag("Potion", $arrow->getDamage()) ]); $diff = ($this->server->getTick() - $this->startAction); $p = $diff / 20; $f = min((($p ** 2) + $p * 2) / 3, 1) * 2; $ev = new EntityShootBowEvent($this, $bow, Entity::createEntity("Arrow", $this->getLevel(), $nbt, $this, $f == 2 ? true : false), $f); if($f < 0.1 or $diff < 5){ $ev->setCancelled(); } $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $ev->getProjectile()->kill(); $this->inventory->sendContents($this); }else{ $ev->getProjectile()->setMotion($ev->getProjectile()->getMotion()->multiply($ev->getForce())); if($this->isSurvival()){ $this->inventory->removeItem($arrow); $bow->setDamage($bow->getDamage() + 1); if($bow->getDamage() >= 385){ $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 0)); }else{ $this->inventory->setItemInHand($bow); } } if($ev->getProjectile() instanceof Projectile){ $this->server->getPluginManager()->callEvent($projectileEv = new ProjectileLaunchEvent($ev->getProjectile())); if($projectileEv->isCancelled()){ $ev->getProjectile()->kill(); }else{ $ev->getProjectile()->spawnToAll(); $this->level->addSound(new LaunchSound($this), $this->getViewers()); } }else{ $ev->getProjectile()->spawnToAll(); } } } }elseif($this->inventory->getItemInHand()->getId() === Item::BUCKET and $this->inventory->getItemInHand()->getDamage() === 1){ //Milk! $this->server->getPluginManager()->callEvent($ev = new PlayerItemConsumeEvent($this, $this->inventory->getItemInHand())); if($ev->isCancelled()){ $this->inventory->sendContents($this); break; } $pk = new EntityEventPacket(); $pk->eid = $this->getId(); $pk->event = EntityEventPacket::USE_ITEM; //$pk; $this->dataPacket($pk); $this->server->broadcastPacket($this->getViewers(), $pk); if($this->isSurvival()){ $slot = $this->inventory->getItemInHand(); --$slot->count; $this->inventory->setItemInHand($slot); $this->inventory->addItem(Item::get(Item::BUCKET, 0, 1)); } $this->removeAllEffects(); }else{ $this->inventory->sendContents($this); } break; case PlayerActionPacket::ACTION_STOP_SLEEPING: $this->stopSleep(); break; case PlayerActionPacket::ACTION_SPAWN_NETHER: break; case PlayerActionPacket::ACTION_SPAWN_SAME_DIMENSION: case PlayerActionPacket::ACTION_SPAWN_OVERWORLD: if($this->isAlive() or !$this->isOnline()){ break; } if($this->server->isHardcore()){ $this->setBanned(true); break; } $this->craftingType = self::CRAFTING_SMALL; if($this->server->netherEnabled){ if($this->level === $this->server->getLevelByName($this->server->netherName)){ $this->teleport($pos = $this->server->getDefaultLevel()->getSafeSpawn()); } } $this->server->getPluginManager()->callEvent($ev = new PlayerRespawnEvent($this, $this->getSpawn())); $this->teleport($ev->getRespawnPosition()); $this->setSprinting(false); $this->setSneaking(false); $this->extinguish(); $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 400); $this->deadTicks = 0; $this->noDamageTicks = 60; $this->removeAllEffects(); $this->setHealth($this->getMaxHealth()); $this->setFood(20); $this->starvationTick = 0; $this->foodTick = 0; $this->foodUsageTime = 0; $this->sendData($this); $this->sendSettings(); $this->inventory->sendContents($this); $this->inventory->sendArmorContents($this); $this->spawnToAll(); $this->scheduleUpdate(); break; case PlayerActionPacket::ACTION_JUMP: break 2; case PlayerActionPacket::ACTION_START_SPRINT: $ev = new PlayerToggleSprintEvent($this, true); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->sendData($this); }else{ $this->setSprinting(true); } break 2; case PlayerActionPacket::ACTION_STOP_SPRINT: $ev = new PlayerToggleSprintEvent($this, false); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->sendData($this); }else{ $this->setSprinting(false); } break 2; case PlayerActionPacket::ACTION_START_SNEAK: $ev = new PlayerToggleSneakEvent($this, true); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->sendData($this); }else{ $this->setSneaking(true); } break 2; case PlayerActionPacket::ACTION_STOP_SNEAK: $ev = new PlayerToggleSneakEvent($this, false); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->sendData($this); }else{ $this->setSneaking(false); } break 2; case PlayerActionPacket::ACTION_START_GLIDE: $ev = new PlayerToggleGlideEvent($this, true); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->sendData($this); }else{ $this->setGliding(true); } break 2; case PlayerActionPacket::ACTION_STOP_GLIDE: $ev = new PlayerToggleGlideEvent($this, false); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->sendData($this); }else{ $this->setGliding(false); } break 2; case PlayerActionPacket::ACTION_CONTINUE_BREAK: $block = $this->level->getBlock($pos); $this->level->broadcastLevelEvent($pos, LevelEventPacket::EVENT_PARTICLE_PUNCH_BLOCK, $block->getId() | ($block->getDamage() << 8) | ($packet->face << 16)); break; default: assert(false, "Unhandled player action " . $packet->action . " from " . $this->getName()); } $this->startAction = -1; $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); break; case ProtocolInfo::REMOVE_BLOCK_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $this->craftingType = self::CRAFTING_SMALL; $vector = new Vector3($packet->x, $packet->y, $packet->z); $item = $this->inventory->getItemInHand(); $oldItem = clone $item; if($this->canInteract($vector->add(0.5, 0.5, 0.5), $this->isCreative() ? 13 : 6) and $this->level->useBreakOn($vector, $item, $this, $this->server->destroyBlockParticle)){ if($this->isSurvival()){ if(!$item->equals($oldItem) or $item->getCount() !== $oldItem->getCount()){ $this->inventory->setItemInHand($item); $this->inventory->sendHeldItem($this); } $this->exhaust(0.025, PlayerExhaustEvent::CAUSE_MINING); } break; } $this->inventory->sendContents($this); $target = $this->level->getBlock($vector); $tile = $this->level->getTile($vector); $this->level->sendBlocks([$this], [$target], UpdateBlockPacket::FLAG_ALL_PRIORITY); $this->inventory->sendHeldItem($this); if($tile instanceof Spawnable){ $tile->spawnTo($this); } break; case ProtocolInfo::MOB_ARMOR_EQUIPMENT_PACKET: //This packet is ignored. Armour changes are also sent by ContainerSetSlotPackets, and are handled there instead. break; case ProtocolInfo::INTERACT_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $this->craftingType = self::CRAFTING_SMALL; $target = $this->level->getEntity($packet->target); $cancelled = false; if($target instanceof Player and $this->server->getConfigBoolean("pvp", true) === false){ $cancelled = true; } if($target instanceof Boat or ($target instanceof Minecart and $target->getType() == Minecart::TYPE_NORMAL)){ if($packet->action === InteractPacket::ACTION_RIGHT_CLICK){ $this->linkEntity($target); }elseif($packet->action === InteractPacket::ACTION_LEFT_CLICK){ if($this->linkedEntity === $target){ $target->setLinked(0, $this); } $target->close(); }elseif($packet->action === InteractPacket::ACTION_LEAVE_VEHICLE){ $this->setLinked(0, $target); } return; } if($packet->action === InteractPacket::ACTION_RIGHT_CLICK){ if($target instanceof Animal and $this->getInventory()->getItemInHand()){ //TODO: Feed } break; }elseif($packet->action === InteractPacket::ACTION_MOUSEOVER){ break; } if($target instanceof Entity and $this->getGamemode() !== Player::VIEW and $this->isAlive() and $target->isAlive()){ if($target instanceof DroppedItem or $target instanceof Arrow){ $this->kick("Attempting to attack an invalid entity"); $this->server->getLogger()->warning($this->getServer()->getLanguage()->translateString("pocketmine.player.invalidEntity", [$this->getName()])); break; } $item = $this->inventory->getItemInHand(); $damage = [ EntityDamageEvent::MODIFIER_BASE => $item->getModifyAttackDamage($target), ]; if(!$this->canInteract($target, 8)){ $cancelled = true; }elseif($target instanceof Player){ if(($target->getGamemode() & 0x01) > 0){ break; }elseif($this->server->getConfigBoolean("pvp") !== true or $this->server->getDifficulty() === 0){ $cancelled = true; } } $ev = new EntityDamageByEntityEvent($this, $target, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $damage, 0.4 + $item->getEnchantmentLevel(Enchantment::TYPE_WEAPON_KNOCKBACK) * 0.15); if($cancelled){ $ev->setCancelled(); } if($target->attack($ev->getFinalDamage(), $ev) === true){ $fireAspectL = $item->getEnchantmentLevel(Enchantment::TYPE_WEAPON_FIRE_ASPECT); if($fireAspectL > 0){ $fireEv = new EntityCombustByEntityEvent($this, $target, $fireAspectL * 4, $ev->getFireProtectL()); Server::getInstance()->getPluginManager()->callEvent($fireEv); if(!$fireEv->isCancelled()){ $target->setOnFire($fireEv->getDuration()); } } //Thorns if($this->isSurvival()){ $ev->createThornsDamage(); if($ev->getThornsDamage() > 0){ $thornsEvent = new EntityDamageByEntityEvent($target, $this, EntityDamageEvent::CAUSE_ENTITY_ATTACK, $ev->getThornsDamage(), 0); if(!$thornsEvent->isCancelled()){ if($this->attack($thornsEvent->getFinalDamage(), $thornsEvent) === true){ $thornsEvent->useArmors(); $ev->setThornsArmorUse(); } } } } $ev->useArmors(); } if($ev->isCancelled()){ if($item->isTool() and $this->isSurvival()){ $this->inventory->sendContents($this); } break; } if($this->isSurvival()){ if($item->isTool()){ if($item->useOn($target) and $item->getDamage() >= $item->getMaxDurability()){ $this->inventory->setItemInHand(Item::get(Item::AIR, 0, 1)); }else{ $this->inventory->setItemInHand($item); } } $this->exhaust(0.3, PlayerExhaustEvent::CAUSE_ATTACK); } } break; case ProtocolInfo::ANIMATE_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $this->server->getPluginManager()->callEvent($ev = new PlayerAnimationEvent($this, $packet->action)); if($ev->isCancelled()){ break; } $pk = new AnimatePacket(); $pk->eid = $this->getId(); $pk->action = $ev->getAnimationType(); $this->server->broadcastPacket($this->getViewers(), $pk); break; case ProtocolInfo::SET_HEALTH_PACKET: //Not used break; case ProtocolInfo::ENTITY_EVENT_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $this->craftingType = self::CRAFTING_SMALL; $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); //TODO: check if this should be true switch($packet->event){ case EntityEventPacket::USE_ITEM: //Eating $slot = $this->inventory->getItemInHand(); if($slot->canBeConsumed()){ $ev = new PlayerItemConsumeEvent($this, $slot); if(!$slot->canBeConsumedBy($this)){ $ev->setCancelled(); } $this->server->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $slot->onConsume($this); }else{ $this->inventory->sendContents($this); } } break; } break; case ProtocolInfo::DROP_ITEM_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } if($packet->item->getId() === Item::AIR){ /** * This is so stupid it's unreal. * Windows 10 Edition Beta drops the contents of the crafting grid when the inventory closes - including air. */ break; } if(($this->isCreative() and $this->server->limitedCreative)){ break; } $this->getTransactionQueue()->addTransaction(new DropItemTransaction($packet->item)); break; case ProtocolInfo::COMMAND_STEP_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $this->craftingType = 0; $commandText = $packet->command; if($packet->inputJson !== null){ foreach($packet->inputJson as $arg){ //command ordering will be an issue if(!is_object($arg)) //anti bot $commandText .= " " . $arg; } } $this->server->getPluginManager()->callEvent($ev = new PlayerCommandPreprocessEvent($this, "/" . $commandText)); if($ev->isCancelled()){ break; } Timings::$playerCommandTimer->startTiming(); $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); Timings::$playerCommandTimer->stopTiming(); break; case ProtocolInfo::TEXT_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $this->craftingType = self::CRAFTING_SMALL; if($packet->type === TextPacket::TYPE_CHAT){ $packet->message = TextFormat::clean($packet->message, $this->removeFormat); foreach(explode("\n", $packet->message) as $message){ if(trim($message) != "" and strlen($message) <= 255 and $this->messageCounter-- > 0){ if(substr($message, 0, 2) === "./"){ //Command (./ = fast hack for old plugins post 0.16) $message = substr($message, 1); } $ev = new PlayerCommandPreprocessEvent($this, $message); if(mb_strlen($ev->getMessage(), "UTF-8") > 320){ $ev->setCancelled(); } $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ break; } if(substr($ev->getMessage(), 0, 1) === "/"){ Timings::$playerCommandTimer->startTiming(); $this->server->dispatchCommand($ev->getPlayer(), substr($ev->getMessage(), 1)); Timings::$playerCommandTimer->stopTiming(); }else{ $this->server->getPluginManager()->callEvent($ev = new PlayerChatEvent($this, $ev->getMessage())); if(!$ev->isCancelled()){ $this->server->broadcastMessage($this->getServer()->getLanguage()->translateString($ev->getFormat(), [ $ev->getPlayer()->getDisplayName(), $ev->getMessage() ]), $ev->getRecipients()); } } } } } break; case ProtocolInfo::CONTAINER_CLOSE_PACKET: if($this->spawned === false or $packet->windowid === 0){ break; } $this->craftingType = self::CRAFTING_SMALL; if(isset($this->windowIndex[$packet->windowid])){ $this->server->getPluginManager()->callEvent(new InventoryCloseEvent($this->windowIndex[$packet->windowid], $this)); $this->removeWindow($this->windowIndex[$packet->windowid]); } /** * Drop anything still left in the crafting inventory * This will usually never be needed since Windows 10 clients will send DropItemPackets * which will cause this to happen anyway, but this is here for when transactions * fail and items end up stuck in the crafting inventory. */ foreach($this->getFloatingInventory()->getContents() as $item){ $this->getFloatingInventory()->removeItem($item); $this->getInventory()->addItem($item); } break; case ProtocolInfo::CRAFTING_EVENT_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } /** * For some annoying reason, anvils send window ID 255 when crafting with them instead of the _actual_ anvil window ID * The result of this is anvils immediately closing when used. This is highly unusual, especially since the * container set slot packets send the correct window ID, but... eh */ /*elseif(!isset($this->windowIndex[$packet->windowId])){ $this->inventory->sendContents($this); $pk = new ContainerClosePacket(); $pk->windowid = $packet->windowId; $this->dataPacket($pk); break; }*/ $recipe = $this->server->getCraftingManager()->getRecipe($packet->id); if($this->craftingType === self::CRAFTING_ANVIL){ $anvilInventory = $this->windowIndex[$packet->windowId] ?? null; if($anvilInventory === null){ foreach($this->windowIndex as $window){ if($window instanceof AnvilInventory){ $anvilInventory = $window; break; } } if($anvilInventory === null){ //If it's _still_ null, then the player doesn't have a valid anvil window, cannot proceed. $this->getServer()->getLogger()->debug("Couldn't find an anvil window for " . $this->getName() . ", exiting"); $this->inventory->sendContents($this); break; } } if($recipe === null){ if($packet->output[0]->getId() > 0 && $packet->output[1] === 0){ //物品重命名 $anvilInventory->onRename($this, $packet->output[0]); }elseif($packet->output[0]->getId() > 0 && $packet->output[1] > 0){ //附魔书 $anvilInventory->process($this, $packet->output[0], $packet->output[1]); } } break; }elseif(($recipe instanceof BigShapelessRecipe or $recipe instanceof BigShapedRecipe) and $this->craftingType === 0){ $this->server->getLogger()->debug("Received big crafting recipe from " . $this->getName() . " with no crafting table open"); $this->inventory->sendContents($this); break; }elseif($recipe === null){ $this->server->getLogger()->debug("Null (unknown) crafting recipe received from " . $this->getName() . " for " . $packet->output[0]); $this->inventory->sendContents($this); break; } $canCraft = true; if(count($packet->input) === 0){ /* If the packet "input" field is empty this needs to be handled differently. * "input" is used to tell the server what items to remove from the client's inventory * Because crafting takes the materials in the crafting grid, nothing needs to be taken from the inventory * Instead, we take the materials from the crafting inventory * To know what materials we need to take, we have to guess the crafting recipe used based on the * output item and the materials stored in the crafting items * The reason we have to guess is because Win10 sometimes sends a different recipe UUID * say, if you put the wood for a door in the right hand side of the crafting grid instead of the left * it will send the recipe UUID for a wooden pressure plate. Unknown currently whether this is a client * bug or if there is something wrong with the way the server handles recipes. * TODO: Remove recipe correction and fix desktop crafting recipes properly. * In fact, TODO: Rewrite crafting entirely. */ $possibleRecipes = $this->server->getCraftingManager()->getRecipesByResult($packet->output[0]); if(!$packet->output[0]->equals($recipe->getResult())){ $this->server->getLogger()->debug("Mismatched desktop recipe received from player " . $this->getName() . ", expected " . $recipe->getResult() . ", got " . $packet->output[0]); } $recipe = null; foreach($possibleRecipes as $r){ /* Check the ingredient list and see if it matches the ingredients we've put into the crafting grid * As soon as we find a recipe that we have all the ingredients for, take it and run with it. */ //Make a copy of the floating inventory that we can make changes to. $floatingInventory = clone $this->floatingInventory; $ingredients = $r->getIngredientList(); //Check we have all the necessary ingredients. foreach($ingredients as $ingredient){ if(!$floatingInventory->contains($ingredient)){ //We're short on ingredients, try the next recipe $canCraft = false; break; } //This will only be reached if we have the item to take away. $floatingInventory->removeItem($ingredient); } if($canCraft){ //Found a recipe that works, take it and run with it. $recipe = $r; break; } } if($recipe !== null){ $this->server->getPluginManager()->callEvent($ev = new CraftItemEvent($this, $ingredients, $recipe)); if($ev->isCancelled()){ $this->inventory->sendContents($this); break; } $this->floatingInventory = $floatingInventory; //Set player crafting inv to the idea one created in this process $this->floatingInventory->addItem(clone $recipe->getResult()); //Add the result to our picture of the crafting inventory }else{ $this->server->getLogger()->debug("Unmatched desktop crafting recipe " . $packet->id . " from player " . $this->getName()); $this->inventory->sendContents($this); break; } }else{ if($recipe instanceof ShapedRecipe){ for($x = 0; $x < 3 and $canCraft; ++$x){ for($y = 0; $y < 3; ++$y){ $item = $packet->input[$y * 3 + $x]; $ingredient = $recipe->getIngredient($x, $y); if($item->getCount() > 0 and $item->getId() > 0){ if($ingredient == null){ $canCraft = false; break; } if($ingredient->getId() != 0 and !$ingredient->equals($item, !$ingredient->hasAnyDamageValue(), $ingredient->hasCompoundTag())){ $canCraft = false; break; } }elseif($ingredient !== null and $item->getId() !== 0){ $canCraft = false; break; } } } }elseif($recipe instanceof ShapelessRecipe){ $needed = $recipe->getIngredientList(); for($x = 0; $x < 3 and $canCraft; ++$x){ for($y = 0; $y < 3; ++$y){ $item = clone $packet->input[$y * 3 + $x]; foreach($needed as $k => $n){ if($n->equals($item, !$n->hasAnyDamageValue(), $n->hasCompoundTag())){ $remove = min($n->getCount(), $item->getCount()); $n->setCount($n->getCount() - $remove); $item->setCount($item->getCount() - $remove); if($n->getCount() === 0){ unset($needed[$k]); } } } if($item->getCount() > 0){ $canCraft = false; break; } } } if(count($needed) > 0){ $canCraft = false; } }else{ $canCraft = false; } /** @var Item[] $ingredients */ $ingredients = $packet->input; $result = $packet->output[0]; if(!$canCraft or !$recipe->getResult()->equals($result)){ $this->server->getLogger()->debug("Unmatched recipe " . $recipe->getId() . " from player " . $this->getName() . ": expected " . $recipe->getResult() . ", got " . $result . ", using: " . implode(", ", $ingredients)); $this->inventory->sendContents($this); break; } $used = array_fill(0, $this->inventory->getSize(), 0); foreach($ingredients as $ingredient){ $slot = -1; foreach($this->inventory->getContents() as $index => $item){ if($ingredient->getId() !== 0 and $ingredient->equals($item, !$ingredient->hasAnyDamageValue(), $ingredient->hasCompoundTag()) and ($item->getCount() - $used[$index]) >= 1){ $slot = $index; $used[$index]++; break; } } if($ingredient->getId() !== 0 and $slot === -1){ $canCraft = false; break; } } if(!$canCraft){ $this->server->getLogger()->debug("Unmatched recipe " . $recipe->getId() . " from player " . $this->getName() . ": client does not have enough items, using: " . implode(", ", $ingredients)); $this->inventory->sendContents($this); break; } $this->server->getPluginManager()->callEvent($ev = new CraftItemEvent($this, $ingredients, $recipe)); if($ev->isCancelled()){ $this->inventory->sendContents($this); break; } foreach($used as $slot => $count){ if($count === 0){ continue; } $item = $this->inventory->getItem($slot); if($item->getCount() > $count){ $newItem = clone $item; $newItem->setCount($item->getCount() - $count); }else{ $newItem = Item::get(Item::AIR, 0, 0); } $this->inventory->setItem($slot, $newItem); } $extraItem = $this->inventory->addItem($recipe->getResult()); if(count($extraItem) > 0 and !$this->isCreative()){ //Could not add all the items to our inventory (not enough space) foreach($extraItem as $item){ $this->level->dropItem($this, $item); } } } switch($recipe->getResult()->getId()){ case Item::WORKBENCH: $this->awardAchievement("buildWorkBench"); break; case Item::WOODEN_PICKAXE: $this->awardAchievement("buildPickaxe"); break; case Item::FURNACE: $this->awardAchievement("buildFurnace"); break; case Item::WOODEN_HOE: $this->awardAchievement("buildHoe"); break; case Item::BREAD: $this->awardAchievement("makeBread"); break; case Item::CAKE: //TODO: detect complex recipes like cake that leave remains $this->awardAchievement("bakeCake"); $this->inventory->addItem(Item::get(Item::BUCKET, 0, 3)); break; case Item::STONE_PICKAXE: case Item::GOLD_PICKAXE: case Item::IRON_PICKAXE: case Item::DIAMOND_PICKAXE: $this->awardAchievement("buildBetterPickaxe"); break; case Item::WOODEN_SWORD: $this->awardAchievement("buildSword"); break; case Item::DIAMOND: $this->awardAchievement("diamond"); break; } break; case ProtocolInfo::CONTAINER_SET_SLOT_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } if($packet->slot < 0){ break; } if($packet->windowid === 0){ //Our inventory if($packet->slot >= $this->inventory->getSize()){ break; } $transaction = new BaseTransaction($this->inventory, $packet->slot, $packet->item); }elseif($packet->windowid === ContainerSetContentPacket::SPECIAL_ARMOR){ //Our armor if($packet->slot >= 4){ break; } $transaction = new BaseTransaction($this->inventory, $packet->slot + $this->inventory->getSize(), $packet->item); }elseif(isset($this->windowIndex[$packet->windowid])){ //Transaction for non-player-inventory window, such as anvil, chest, etc. $inv = $this->windowIndex[$packet->windowid]; $achievements = []; if($inv instanceof FurnaceInventory and $inv->getItem($packet->slot)->getId() === Item::IRON_INGOT and $packet->slot === FurnaceInventory::RESULT){ $achievements[] = "acquireIron"; }elseif($inv instanceof EnchantInventory and $packet->item->hasEnchantments()){ $inv->onEnchant($this, $inv->getItem($packet->slot), $packet->item); } $transaction = new BaseTransaction($inv, $packet->slot, $packet->item, $achievements); }else{ //Client sent a transaction for a window which the server doesn't think they have open break; } $this->getTransactionQueue()->addTransaction($transaction); break; case ProtocolInfo::BLOCK_ENTITY_DATA_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $this->craftingType = self::CRAFTING_SMALL; $pos = new Vector3($packet->x, $packet->y, $packet->z); if($pos->distanceSquared($this) > 10000){ break; } $t = $this->level->getTile($pos); if($t instanceof Spawnable){ $nbt = new NBT(NBT::LITTLE_ENDIAN); $nbt->read($packet->namedtag, false, true); $nbt = $nbt->getData(); if(!$t->updateCompoundTag($nbt, $this)){ $t->spawnTo($this); } } break; case ProtocolInfo::REQUEST_CHUNK_RADIUS_PACKET: $this->setViewDistance($packet->radius); break; case ProtocolInfo::SET_PLAYER_GAME_TYPE_PACKET: if($packet->gamemode !== $this->gamemode){ //Set this back to default. TODO: handle this properly $this->sendGamemode(); $this->sendSettings(); } break; case ProtocolInfo::ITEM_FRAME_DROP_ITEM_PACKET: if($this->spawned === false or !$this->isAlive()){ break; } $tile = $this->level->getTile($this->temporalVector->setComponents($packet->x, $packet->y, $packet->z)); if($tile instanceof ItemFrame){ $this->server->getPluginManager()->callEvent($ev = new ItemFrameDropItemEvent($this, $tile->getBlock(), $tile, $tile->getItem())); if($this->isSpectator() or $ev->isCancelled()){ $tile->spawnTo($this); break; } if(lcg_value() <= $tile->getItemDropChance()){ $this->level->dropItem($tile->getBlock(), $tile->getItem()); } $tile->setItem(null); $tile->setItemRotation(0); } break; default: break; } $timings->stopTiming(); } /** * Kicks a player from the server * * @param string $reason * @param bool $isAdmin * * @return bool */ public function kick($reason = "", $isAdmin = true){ $this->server->getPluginManager()->callEvent($ev = new PlayerKickEvent($this, $reason, $this->getLeaveMessage())); if(!$ev->isCancelled()){ if($isAdmin){ $message = "Kicked by admin." . ($reason !== "" ? " Reason: " . $reason : ""); }else{ if($reason === ""){ $message = "disconnectionScreen.noReason"; }else{ $message = $reason; } } $this->close($ev->getQuitMessage(), $message); return true; } return false; } /** @var string[] */ private $messageQueue = []; /** * @param Item $item * * Drops the specified item in front of the player. */ public function dropItem(Item $item){ if($this->spawned === false or !$this->isAlive()){ return; } if(($this->isCreative() and $this->server->limitedCreative) or $this->isSpectator()){ //Ignore for limited creative return; } if($item->getId() === Item::AIR or $item->getCount() < 1){ //Ignore dropping air or items with bad counts return; } $ev = new PlayerDropItemEvent($this, $item); $this->server->getPluginManager()->callEvent($ev); if($ev->isCancelled()){ $this->getFloatingInventory()->removeItem($item); $this->getInventory()->addItem($item); return; } $motion = $this->getDirectionVector()->multiply(0.4); $this->level->dropItem($this->add(0, 1.3, 0), $item, $motion, 40); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ACTION, false); } /** * Adds a title text to the user's screen, with an optional subtitle. * * @param string $title * @param string $subtitle * @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used. * @param int $stay Duration in ticks to stay on screen for * @param int $fadeOut Duration in ticks for fade-out. */ public function sendActionBar(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1){ $this->setTitleDuration($fadeIn, $stay, $fadeOut); if($subtitle !== ""){ $this->sendTitleText($subtitle, SetTitlePacket::TYPE_SUB_TITLE); } $this->sendTitleText($title, SetTitlePacket::TYPE_TITLE); } /*********/ /** * @param string $title * @param string $subtitle * @param int $fadeIn * @param int $stay * @param int $fadeOut */ public function addTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1){ $this->setTitleDuration($fadeIn, $stay, $fadeOut); if($subtitle !== ""){ $this->sendTitleText($subtitle, SetTitlePacket::TYPE_SUB_TITLE); } $this->sendTitleText($title, SetTitlePacket::TYPE_TITLE); } /** * Adds small text to the user's screen. * * @param string $message */ public function addActionBarMessage(string $message){ $this->sendTitleText($message, SetTitlePacket::TYPE_ACTION_BAR); } /** * Removes the title from the client's screen. */ public function removeTitles(){ $pk = new SetTitlePacket(); $pk->type = SetTitlePacket::TYPE_CLEAR; $this->dataPacket($pk); } /** * Sets the title duration. * * @param int $fadeIn Title fade-in time in ticks. * @param int $stay Title stay time in ticks. * @param int $fadeOut Title fade-out time in ticks. */ public function setTitleDuration(int $fadeIn, int $stay, int $fadeOut){ if($fadeIn >= 0 and $stay >= 0 and $fadeOut >= 0){ $pk = new SetTitlePacket(); $pk->type = SetTitlePacket::TYPE_TIMES; $pk->fadeInDuration = $fadeIn; $pk->duration = $stay; $pk->fadeOutDuration = $fadeOut; $this->dataPacket($pk); } } /** * Internal function used for sending titles. * * @param string $title * @param int $type */ protected function sendTitleText(string $title, int $type){ $pk = new SetTitlePacket(); $pk->type = $type; $pk->title = $title; $this->dataPacket($pk); } /** * @param string $address * @param $port */ public function transfer(string $address, $port){ $pk = new TransferPacket(); $pk->address = $address; $pk->port = $port; $this->dataPacket($pk); } /** * Sends a direct chat message to a player * * @param string|TextContainer $message * * @return bool */ public function sendMessage($message){ if($message instanceof TextContainer){ if($message instanceof TranslationContainer){ $this->sendTranslation($message->getText(), $message->getParameters()); return false; } $message = $message->getText(); } //TODO: Remove this workaround (broken client MCPE 1.0.0) $this->messageQueue[] = $this->server->getLanguage()->translateString($message); /* $pk = new TextPacket(); $pk->type = TextPacket::TYPE_RAW; $pk->message = $this->server->getLanguage()->translateString($message); $this->dataPacket($pk); */ } /** * @param $message * @param array $parameters * * @return bool */ public function sendTranslation($message, array $parameters = []){ $pk = new TextPacket(); if(!$this->server->isLanguageForced()){ $pk->type = TextPacket::TYPE_TRANSLATION; $pk->message = $this->server->getLanguage()->translateString($message, $parameters, "pocketmine."); foreach($parameters as $i => $p){ $parameters[$i] = $this->server->getLanguage()->translateString($p, $parameters, "pocketmine."); } $pk->parameters = $parameters; }else{ $pk->type = TextPacket::TYPE_RAW; $pk->message = $this->server->getLanguage()->translateString($message, $parameters); } $ev = new PlayerTextPreSendEvent($this, $pk->message, PlayerTextPreSendEvent::TRANSLATED_MESSAGE); $this->server->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $this->dataPacket($pk); return true; } return false; } /** * @param $message * @param string $subtitle * * @return bool */ public function sendPopup($message, $subtitle = ""){ $ev = new PlayerTextPreSendEvent($this, $message, PlayerTextPreSendEvent::POPUP); $this->server->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $pk = new TextPacket(); $pk->type = TextPacket::TYPE_POPUP; $pk->source = $ev->getMessage(); $pk->message = $subtitle; $this->dataPacket($pk); return true; } return false; } /** * @param $message * * @return bool */ public function sendTip($message){ $ev = new PlayerTextPreSendEvent($this, $message, PlayerTextPreSendEvent::TIP); $this->server->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $pk = new TextPacket(); $pk->type = TextPacket::TYPE_TIP; $pk->message = $ev->getMessage(); $this->dataPacket($pk); return true; } return false; } /** * Send a title text or/and with/without a sub title text to a player * * @param $title * @param string $subtitle * @param int $fadein * @param int $fadeout * @param int $duration * * @return bool */ public function sendTitle($title, $subtitle = "", $fadein = 20, $fadeout = 20, $duration = 5){ return $this->addTitle($title, $subtitle, $fadein, $duration, $fadeout); } /** * Note for plugin developers: use kick() with the isAdmin * flag set to kick without the "Kicked by admin" part instead of this method. * * @param string $message Message to be broadcasted * @param string $reason Reason showed in console * @param bool $notify */ public final function close($message = "", $reason = "generic reason", $notify = true){ if($this->connected and !$this->closed){ if($notify and strlen((string) $reason) > 0){ $pk = new DisconnectPacket(); $pk->hideDisconnectionScreen = null; $pk->message = $reason; $this->dataPacket($pk); } //$this->setLinked(); if($this->fishingHook instanceof FishingHook){ $this->fishingHook->close(); $this->fishingHook = null; } $this->removeEffect(Effect::HEALTH_BOOST); $this->connected = false; if(strlen($this->getName()) > 0){ $this->server->getPluginManager()->callEvent($ev = new PlayerQuitEvent($this, $message, true)); if($this->loggedIn === true and $ev->getAutoSave()){ $this->save(); } } foreach($this->server->getOnlinePlayers() as $player){ if(!$player->canSee($this)){ $player->showPlayer($this); } } $this->hiddenPlayers = []; foreach($this->windowIndex as $window){ $this->removeWindow($window); } foreach($this->usedChunks as $index => $d){ Level::getXZ($index, $chunkX, $chunkZ); $this->level->unregisterChunkLoader($this, $chunkX, $chunkZ); foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){ $entity->despawnFrom($this, false); } unset($this->usedChunks[$index]); } parent::close(); $this->interface->close($this, $notify ? $reason : ""); if($this->loggedIn){ $this->server->removeOnlinePlayer($this); } $this->loggedIn = false; $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_USERS, $this); $this->server->getPluginManager()->unsubscribeFromPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this); if(isset($ev) and $this->username != "" and $this->spawned !== false and $ev->getQuitMessage() != ""){ if($this->server->playerMsgType === Server::PLAYER_MSG_TYPE_MESSAGE) $this->server->broadcastMessage($ev->getQuitMessage()); elseif($this->server->playerMsgType === Server::PLAYER_MSG_TYPE_TIP) $this->server->broadcastTip(str_replace("@player", $this->getName(), $this->server->playerLogoutMsg)); elseif($this->server->playerMsgType === Server::PLAYER_MSG_TYPE_POPUP) $this->server->broadcastPopup(str_replace("@player", $this->getName(), $this->server->playerLogoutMsg)); } $this->spawned = false; $this->server->getLogger()->info($this->getServer()->getLanguage()->translateString("pocketmine.player.logOut", [ TextFormat::AQUA . $this->getName() . TextFormat::WHITE, $this->ip, $this->port, $this->getServer()->getLanguage()->translateString($reason) ])); $this->windows = new \SplObjectStorage(); $this->windowIndex = []; $this->usedChunks = []; $this->loadQueue = []; $this->hasSpawned = []; $this->spawnPosition = null; if($this->server->dserverConfig["enable"] and $this->server->dserverConfig["queryAutoUpdate"]) $this->server->updateQuery(); } if($this->perm !== null){ $this->perm->clearPermissions(); $this->perm = null; } $this->inventory = null; $this->floatingInventory = null; $this->enderChestInventory = null; $this->transactionQueue = null; $this->chunk = null; $this->server->removePlayer($this); } /** * @return array */ public function __debugInfo(){ return []; } /** * Handles player data saving * * @param bool $async */ public function save($async = false){ if($this->closed){ throw new \InvalidStateException("Tried to save closed player"); } parent::saveNBT(); if($this->level instanceof Level){ $this->namedtag->Level = new StringTag("Level", $this->level->getName()); if($this->hasValidSpawnPosition()){ $this->namedtag["SpawnLevel"] = $this->spawnPosition->getLevel()->getName(); $this->namedtag["SpawnX"] = (int) $this->spawnPosition->x; $this->namedtag["SpawnY"] = (int) $this->spawnPosition->y; $this->namedtag["SpawnZ"] = (int) $this->spawnPosition->z; } foreach($this->achievements as $achievement => $status){ $this->namedtag->Achievements[$achievement] = new ByteTag($achievement, $status === true ? 1 : 0); } $this->namedtag["playerGameType"] = $this->gamemode; $this->namedtag["lastPlayed"] = new LongTag("lastPlayed", floor(microtime(true) * 1000)); $this->namedtag["Health"] = new ShortTag("Health", $this->getHealth()); $this->namedtag["MaxHealth"] = new ShortTag("MaxHealth", $this->getMaxHealth()); if($this->username != "" and $this->namedtag instanceof CompoundTag){ $this->server->saveOfflinePlayerData($this->username, $this->namedtag, $async); } } } /** * Gets the username * * @return string */ public function getName(){ return $this->username; } public function kill(){ if(!$this->spawned){ return; } $message = "death.attack.generic"; $params = [ $this->getDisplayName() ]; $cause = $this->getLastDamageCause(); switch($cause === null ? EntityDamageEvent::CAUSE_CUSTOM : $cause->getCause()){ case EntityDamageEvent::CAUSE_ENTITY_ATTACK: if($cause instanceof EntityDamageByEntityEvent){ $e = $cause->getDamager(); if($e instanceof Player){ $message = "death.attack.player"; $params[] = $e->getDisplayName(); break; }elseif($e instanceof Living){ $message = "death.attack.mob"; $params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName(); break; }else{ $params[] = "Unknown"; } } break; case EntityDamageEvent::CAUSE_PROJECTILE: if($cause instanceof EntityDamageByEntityEvent){ $e = $cause->getDamager(); if($e instanceof Player){ $message = "death.attack.arrow"; $params[] = $e->getDisplayName(); }elseif($e instanceof Living){ $message = "death.attack.arrow"; $params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName(); break; }else{ $params[] = "Unknown"; } } break; case EntityDamageEvent::CAUSE_SUICIDE: $message = "death.attack.generic"; break; case EntityDamageEvent::CAUSE_VOID: $message = "death.attack.outOfWorld"; break; case EntityDamageEvent::CAUSE_FALL: if($cause instanceof EntityDamageEvent){ if($cause->getFinalDamage() > 2){ $message = "death.fell.accident.generic"; break; } } $message = "death.attack.fall"; break; case EntityDamageEvent::CAUSE_SUFFOCATION: $message = "death.attack.inWall"; break; case EntityDamageEvent::CAUSE_LAVA: $message = "death.attack.lava"; break; case EntityDamageEvent::CAUSE_FIRE: $message = "death.attack.onFire"; break; case EntityDamageEvent::CAUSE_FIRE_TICK: $message = "death.attack.inFire"; break; case EntityDamageEvent::CAUSE_DROWNING: $message = "death.attack.drown"; break; case EntityDamageEvent::CAUSE_CONTACT: if($cause instanceof EntityDamageByBlockEvent){ if($cause->getDamager()->getId() === Block::CACTUS){ $message = "death.attack.cactus"; } } break; case EntityDamageEvent::CAUSE_BLOCK_EXPLOSION: case EntityDamageEvent::CAUSE_ENTITY_EXPLOSION: if($cause instanceof EntityDamageByEntityEvent){ $e = $cause->getDamager(); if($e instanceof Player){ $message = "death.attack.explosion.player"; $params[] = $e->getDisplayName(); }elseif($e instanceof Living){ $message = "death.attack.explosion.player"; $params[] = $e->getNameTag() !== "" ? $e->getNameTag() : $e->getName(); break; } }else{ $message = "death.attack.explosion"; } break; case EntityDamageEvent::CAUSE_MAGIC: $message = "death.attack.magic"; break; case EntityDamageEvent::CAUSE_CUSTOM: break; default: break; } Entity::kill(); $ev = new PlayerDeathEvent($this, $this->getDrops(), new TranslationContainer($message, $params)); $ev->setKeepInventory($this->server->keepInventory); $ev->setKeepExperience($this->server->keepExperience); $this->server->getPluginManager()->callEvent($ev); if(!$ev->getKeepInventory()){ foreach($ev->getDrops() as $item){ $this->level->dropItem($this, $item); } if($this->floatingInventory !== null){ $this->floatingInventory->clearAll(); } if($this->inventory !== null){ $this->inventory->clearAll(); } } if($this->server->expEnabled and !$ev->getKeepExperience()){ $exp = min(91, $this->getTotalXp()); //Max 7 levels of exp dropped $this->getLevel()->spawnXPOrb($this->add(0, 0.2, 0), $exp); $this->setTotalXp(0, true); } if($ev->getDeathMessage() != ""){ $this->server->broadcast($ev->getDeathMessage(), Server::BROADCAST_CHANNEL_USERS); } $pos = $this->getSpawn(); $this->setHealth(0); $pk = new RespawnPacket(); $pk->x = $pos->x; $pk->y = $pos->y; $pk->z = $pos->z; $this->dataPacket($pk); } /** * @param int $amount */ public function setHealth($amount){ parent::setHealth($amount); if($this->spawned === true){ $this->foodTick = 0; $this->getAttributeMap()->getAttribute(Attribute::HEALTH)->setMaxValue($this->getMaxHealth())->setValue($amount, true); } } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool */ public function attack($damage, EntityDamageEvent $source){ if(!$this->isAlive()){ return false; } if($this->isCreative() and $source->getCause() !== EntityDamageEvent::CAUSE_MAGIC and $source->getCause() !== EntityDamageEvent::CAUSE_SUICIDE and $source->getCause() !== EntityDamageEvent::CAUSE_VOID ){ $source->setCancelled(); }elseif($this->allowFlight and $source->getCause() === EntityDamageEvent::CAUSE_FALL){ $source->setCancelled(); } parent::attack($damage, $source); if($source->isCancelled()){ return false; }elseif($this->getLastDamageCause() === $source and $this->spawned){ $pk = new EntityEventPacket(); $pk->eid = $this->id; $pk->event = EntityEventPacket::HURT_ANIMATION; $this->dataPacket($pk); if($this->isSurvival()){ $this->exhaust(0.3, PlayerExhaustEvent::CAUSE_DAMAGE); } } return true; } /** * @param Vector3 $pos * @param null $yaw * @param null $pitch * @param int $mode * @param array|null $targets */ public function sendPosition(Vector3 $pos, $yaw = null, $pitch = null, $mode = MovePlayerPacket::MODE_NORMAL, array $targets = null){ $yaw = $yaw === null ? $this->yaw : $yaw; $pitch = $pitch === null ? $this->pitch : $pitch; $pk = new MovePlayerPacket(); $pk->eid = $this->getId(); $pk->x = $pos->x; $pk->y = $pos->y + $this->getEyeHeight(); $pk->z = $pos->z; $pk->bodyYaw = $yaw; $pk->pitch = $pitch; $pk->yaw = $yaw; $pk->mode = $mode; if($targets !== null){ $this->server->broadcastPacket($targets, $pk); }else{ $this->dataPacket($pk); } $this->newPosition = null; } protected function checkChunks(){ if($this->chunk === null or ($this->chunk->getX() !== ($this->x >> 4) or $this->chunk->getZ() !== ($this->z >> 4))){ if($this->chunk !== null){ $this->chunk->removeEntity($this); } $this->chunk = $this->level->getChunk($this->x >> 4, $this->z >> 4, true); if(!$this->justCreated){ $newChunk = $this->level->getChunkPlayers($this->x >> 4, $this->z >> 4); unset($newChunk[$this->getLoaderId()]); /** @var Player[] $reload */ $reload = []; foreach($this->hasSpawned as $player){ if(!isset($newChunk[$player->getLoaderId()])){ $this->despawnFrom($player); }else{ unset($newChunk[$player->getLoaderId()]); $reload[] = $player; } } foreach($newChunk as $player){ $this->spawnTo($player); } } if($this->chunk === null){ return; } $this->chunk->addEntity($this); } } /** * @return bool */ protected function checkTeleportPosition(){ if($this->teleportPosition !== null){ $chunkX = $this->teleportPosition->x >> 4; $chunkZ = $this->teleportPosition->z >> 4; for($X = -1; $X <= 1; ++$X){ for($Z = -1; $Z <= 1; ++$Z){ if(!isset($this->usedChunks[$index = Level::chunkHash($chunkX + $X, $chunkZ + $Z)]) or $this->usedChunks[$index] === false){ return false; } } } $this->sendPosition($this, null, null, MovePlayerPacket::MODE_RESET); $this->spawnToAll(); $this->forceMovement = $this->teleportPosition; $this->teleportPosition = null; return true; } return true; } /** * @param Vector3|Position|Location $pos * @param float $yaw * @param float $pitch * * @return bool */ public function teleport(Vector3 $pos, $yaw = null, $pitch = null){ if(!$this->isOnline()){ return false; } $oldPos = $this->getPosition(); if(parent::teleport($pos, $yaw, $pitch)){ foreach($this->windowIndex as $window){ if($window === $this->inventory){ continue; } $this->removeWindow($window); } $this->teleportPosition = new Vector3($this->x, $this->y, $this->z); if(!$this->checkTeleportPosition()){ $this->forceMovement = $oldPos; }else{ $this->spawnToAll(); } $this->resetFallDistance(); $this->nextChunkOrderRun = 0; $this->newPosition = null; $this->stopSleep(); return true; } return false; } /** * This method may not be reliable. Clients don't like to be moved into unloaded chunks. * Use teleport() for a delayed teleport after chunks have been sent. * * @param Vector3 $pos * @param float $yaw * @param float $pitch */ public function teleportImmediate(Vector3 $pos, $yaw = null, $pitch = null){ if(parent::teleport($pos, $yaw, $pitch)){ foreach($this->windowIndex as $window){ if($window === $this->inventory){ continue; } $this->removeWindow($window); } $this->forceMovement = new Vector3($this->x, $this->y, $this->z); $this->sendPosition($this, $this->yaw, $this->pitch, MovePlayerPacket::MODE_RESET); $this->resetFallDistance(); $this->orderChunks(); $this->nextChunkOrderRun = 0; $this->newPosition = null; } } /** * @param Inventory $inventory * * @return int */ public function getWindowId(Inventory $inventory) : int{ if($this->windows->contains($inventory)){ return $this->windows[$inventory]; } return -1; } /** * Returns the created/existing window id * * @param Inventory $inventory * @param int $forceId * * @return int */ public function addWindow(Inventory $inventory, $forceId = null) : int{ if($this->windows->contains($inventory)){ return $this->windows[$inventory]; } if($forceId === null){ $this->windowCnt = $cnt = max(2, ++$this->windowCnt % 99); }else{ $cnt = (int) $forceId; } $this->windowIndex[$cnt] = $inventory; $this->windows->attach($inventory, $cnt); if($inventory->open($this)){ return $cnt; }else{ $this->removeWindow($inventory); return -1; } } /** * @param Inventory $inventory */ public function removeWindow(Inventory $inventory){ $inventory->close($this); if($this->windows->contains($inventory)){ $id = $this->windows[$inventory]; $this->windows->detach($this->windowIndex[$id]); unset($this->windowIndex[$id]); } } /** * @param string $metadataKey * @param MetadataValue $metadataValue */ public function setMetadata($metadataKey, MetadataValue $metadataValue){ $this->server->getPlayerMetadata()->setMetadata($this, $metadataKey, $metadataValue); } /** * @param string $metadataKey * * @return MetadataValue[] */ public function getMetadata($metadataKey){ return $this->server->getPlayerMetadata()->getMetadata($this, $metadataKey); } /** * @param string $metadataKey * * @return bool */ public function hasMetadata($metadataKey){ return $this->server->getPlayerMetadata()->hasMetadata($this, $metadataKey); } /** * @param string $metadataKey * @param Plugin $plugin */ public function removeMetadata($metadataKey, Plugin $plugin){ $this->server->getPlayerMetadata()->removeMetadata($this, $metadataKey, $plugin); } /** * @param Chunk $chunk */ public function onChunkChanged(Chunk $chunk){ if(isset($this->usedChunks[$hash = Level::chunkHash($chunk->getX(), $chunk->getZ())])){ $this->usedChunks[$hash] = false; } if(!$this->spawned){ $this->nextChunkOrderRun = 0; } } /** * @param Chunk $chunk */ public function onChunkLoaded(Chunk $chunk){ } /** * @param Chunk $chunk */ public function onChunkPopulated(Chunk $chunk){ } /** * @param Chunk $chunk */ public function onChunkUnloaded(Chunk $chunk){ } /** * @param Vector3 $block */ public function onBlockChanged(Vector3 $block){ } /** * @return int|null */ public function getLoaderId(){ return $this->loaderId; } /** * @return bool */ public function isLoaderActive(){ return $this->isConnected(); } /** * @param Effect $effect * * @return bool|void * @internal param $Effect */ public function addEffect(Effect $effect){//Overwrite if($effect->isBad() && $this->isCreative()){ return; } parent::addEffect($effect); } } $value){ echo str_repeat(" ", $cnt + 1) . "[" . (is_int($key) ? $key : '"' . $key . '"') . "]=>" . PHP_EOL; ++$cnt; safe_var_dump($value); --$cnt; } echo str_repeat(" ", $cnt) . "}" . PHP_EOL; break; case is_int($var): echo str_repeat(" ", $cnt) . "int(" . $var . ")" . PHP_EOL; break; case is_float($var): echo str_repeat(" ", $cnt) . "float(" . $var . ")" . PHP_EOL; break; case is_bool($var): echo str_repeat(" ", $cnt) . "bool(" . ($var === true ? "true" : "false") . ")" . PHP_EOL; break; case is_string($var): echo str_repeat(" ", $cnt) . "string(" . strlen($var) . ") \"$var\"" . PHP_EOL; break; case is_resource($var): echo str_repeat(" ", $cnt) . "resource() of type (" . get_resource_type($var) . ")" . PHP_EOL; break; case is_object($var): echo str_repeat(" ", $cnt) . "object(" . get_class($var) . ")" . PHP_EOL; break; case is_null($var): echo str_repeat(" ", $cnt) . "NULL" . PHP_EOL; break; } } } function dummy(){ } } namespace pocketmine { use pocketmine\utils\Binary; use pocketmine\utils\MainLogger; use pocketmine\utils\ServerKiller; use pocketmine\utils\Terminal; use pocketmine\utils\Utils; use pocketmine\wizard\Installer; const VERSION = "1.1dev"; const API_VERSION = "3.0.1"; const CODENAME = "LlamaSpit"; const GENISYS_API_VERSION = '2.0.0'; /* * Startup code. Do not look at it, it may harm you. * Most of them are hacks to fix date-related bugs, or basic functions used after this * This is the only non-class based file on this project. * Enjoy it as much as I did writing it. I don't want to do it again. */ if(\Phar::running(true) !== ""){ @define('pocketmine\PATH', \Phar::running(true) . "/"); }else{ @define('pocketmine\PATH', \getcwd() . DIRECTORY_SEPARATOR); } if(version_compare("7.0", PHP_VERSION) > 0){ echo "[CRITICAL] You must use PHP >= 7.0" . PHP_EOL; echo "[CRITICAL] Please use the installer provided on the homepage." . PHP_EOL; exit(1); } if(!extension_loaded("pthreads")){ echo "[CRITICAL] Unable to find the pthreads extension." . PHP_EOL; echo "[CRITICAL] Please use the installer provided on the homepage." . PHP_EOL; exit(1); } if(!class_exists("ClassLoader", false)){ require_once(\pocketmine\PATH . "src/spl/ClassLoader.php"); require_once(\pocketmine\PATH . "src/spl/BaseClassLoader.php"); } $autoloader = new \BaseClassLoader(); $autoloader->addPath(\pocketmine\PATH . "src"); $autoloader->addPath(\pocketmine\PATH . "src" . DIRECTORY_SEPARATOR . "spl"); $autoloader->register(true); set_time_limit(0); //Who set it to 30 seconds?!?! gc_enable(); error_reporting(-1); ini_set("allow_url_fopen", 1); ini_set("display_errors", 1); ini_set("display_startup_errors", 1); ini_set("default_charset", "utf-8"); ini_set("memory_limit", -1); define('pocketmine\START_TIME', microtime(true)); $opts = getopt("", ["data:", "plugins:", "no-wizard", "enable-profiler"]); define('pocketmine\DATA', isset($opts["data"]) ? $opts["data"] . DIRECTORY_SEPARATOR : \getcwd() . DIRECTORY_SEPARATOR); define('pocketmine\PLUGIN_PATH', isset($opts["plugins"]) ? $opts["plugins"] . DIRECTORY_SEPARATOR : \getcwd() . DIRECTORY_SEPARATOR . "plugins" . DIRECTORY_SEPARATOR); Terminal::init(); define('pocketmine\ANSI', Terminal::hasFormattingCodes()); if(!file_exists(\pocketmine\DATA)){ mkdir(\pocketmine\DATA, 0777, true); } //Logger has a dependency on timezone, so we'll set it to UTC until we can get the actual timezone. date_default_timezone_set("UTC"); $logger = new MainLogger(\pocketmine\DATA . "server.log", \pocketmine\ANSI); if(!ini_get("date.timezone")){ if(($timezone = detect_system_timezone()) and date_default_timezone_set($timezone)){ //Success! Timezone has already been set and validated in the if statement. //This here is just for redundancy just in case some program wants to read timezone data from the ini. ini_set("date.timezone", $timezone); }else{ //If system timezone detection fails or timezone is an invalid value. if($response = Utils::getURL("http://ip-api.com/json") and $ip_geolocation_data = json_decode($response, true) and $ip_geolocation_data['status'] !== 'fail' and date_default_timezone_set($ip_geolocation_data['timezone']) ){ //Again, for redundancy. ini_set("date.timezone", $ip_geolocation_data['timezone']); }else{ ini_set("date.timezone", "UTC"); date_default_timezone_set("UTC"); $logger->warning("Timezone could not be automatically determined. An incorrect timezone will result in incorrect timestamps on console logs. It has been set to \"UTC\" by default. You can change it on the php.ini file."); } } }else{ /* * This is here so that people don't come to us complaining and fill up the issue tracker when they put * an incorrect timezone abbreviation in php.ini apparently. */ $timezone = ini_get("date.timezone"); if(strpos($timezone, "/") === false){ $default_timezone = timezone_name_from_abbr($timezone); ini_set("date.timezone", $default_timezone); date_default_timezone_set($default_timezone); }else{ date_default_timezone_set($timezone); } } /** * @return bool|string */ function detect_system_timezone(){ switch(Utils::getOS()){ case 'win': $regex = '/(UTC)(\+*\-*\d*\d*\:*\d*\d*)/'; /* * wmic timezone get Caption * Get the timezone offset * * Sample Output var_dump * array(3) { * [0] => * string(7) "Caption" * [1] => * string(20) "(UTC+09:30) Adelaide" * [2] => * string(0) "" * } */ exec("wmic timezone get Caption", $output); $string = trim(implode("\n", $output)); //Detect the Time Zone string preg_match($regex, $string, $matches); if(!isset($matches[2])){ return false; } $offset = $matches[2]; if($offset == ""){ return "UTC"; } return parse_offset($offset); case 'linux': // Ubuntu / Debian. if(file_exists('/etc/timezone')){ $data = file_get_contents('/etc/timezone'); if($data){ return trim($data); } } // RHEL / CentOS if(file_exists('/etc/sysconfig/clock')){ $data = parse_ini_file('/etc/sysconfig/clock'); if(!empty($data['ZONE'])){ return trim($data['ZONE']); } } //Portable method for incompatible linux distributions. $offset = trim(exec('date +%:z')); if($offset == "+00:00"){ return "UTC"; } return parse_offset($offset); case 'mac': if(is_link('/etc/localtime')){ $filename = readlink('/etc/localtime'); if(strpos($filename, '/usr/share/zoneinfo/') === 0){ $timezone = substr($filename, 20); return trim($timezone); } } return false; default: return false; } } /** * @param string $offset In the format of +09:00, +02:00, -04:00 etc. * * @return string */ function parse_offset($offset){ //Make signed offsets unsigned for date_parse if(strpos($offset, '-') !== false){ $negative_offset = true; $offset = str_replace('-', '', $offset); }else{ if(strpos($offset, '+') !== false){ $negative_offset = false; $offset = str_replace('+', '', $offset); }else{ return false; } } $parsed = date_parse($offset); $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; //After date_parse is done, put the sign back if($negative_offset == true){ $offset = -abs($offset); } //And then, look the offset up. //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird. //That's been a bug in PHP since 2008! foreach(timezone_abbreviations_list() as $zones){ foreach($zones as $timezone){ if($timezone['offset'] == $offset){ return $timezone['timezone_id']; } } } return false; } if(isset($opts["enable-profiler"])){ if(function_exists("profiler_enable")){ \profiler_enable(); $logger->notice("Execution is being profiled"); }else{ $logger->notice("No profiler found. Please install https://github.com/krakjoe/profiler"); } } /** * @param $pid */ function kill($pid){ switch(Utils::getOS()){ case "win": exec("taskkill.exe /F /PID " . ((int) $pid) . " > NUL"); break; case "mac": case "linux": default: if(function_exists("posix_kill")){ posix_kill($pid, SIGKILL); }else{ exec("kill -9 " . ((int) $pid) . " > /dev/null 2>&1"); } } } /** * @param object $value * @param bool $includeCurrent * * @return int */ function getReferenceCount($value, $includeCurrent = true){ ob_start(); debug_zval_dump($value); $ret = explode("\n", ob_get_contents()); ob_end_clean(); if(count($ret) >= 1 and preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){ return ((int) $m[1]) - ($includeCurrent ? 3 : 4); //$value + zval call + extra call } return -1; } /** * @param int $start * @param null $trace * * @return array */ function getTrace($start = 1, $trace = null){ if($trace === null){ if(function_exists("xdebug_get_function_stack")){ $trace = array_reverse(xdebug_get_function_stack()); }else{ $e = new \Exception(); $trace = $e->getTrace(); } } $messages = []; $j = 0; for($i = (int) $start; isset($trace[$i]); ++$i, ++$j){ $params = ""; if(isset($trace[$i]["args"]) or isset($trace[$i]["params"])){ if(isset($trace[$i]["args"])){ $args = $trace[$i]["args"]; }else{ $args = $trace[$i]["params"]; } foreach($args as $name => $value){ $params .= (is_object($value) ? get_class($value) . " " . (method_exists($value, "__toString") ? $value->__toString() : "object") : gettype($value) . " " . (is_array($value) ? "Array()" : Utils::printable(@strval($value)))) . ", "; } } $messages[] = "#$j " . (isset($trace[$i]["file"]) ? cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" or $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable(substr($params, 0, -2)) . ")"; } return $messages; } /** * @param $path * * @return string */ function cleanPath($path){ return rtrim(str_replace(["\\", ".php", "phar://", rtrim(str_replace(["\\", "phar://"], ["/", ""], \pocketmine\PATH), "/"), rtrim(str_replace(["\\", "phar://"], ["/", ""], \pocketmine\PLUGIN_PATH), "/")], ["/", "", "", "", ""], $path), "/"); } $errors = 0; if(php_sapi_name() !== "cli"){ $logger->critical("You must run GenisysPro using the CLI."); ++$errors; } if(!extension_loaded("sockets")){ $logger->critical("Unable to find the Socket extension."); ++$errors; } $pthreads_version = phpversion("pthreads"); if(substr_count($pthreads_version, ".") < 2){ $pthreads_version = "0.$pthreads_version"; } if(version_compare($pthreads_version, "3.1.5") < 0){ $logger->critical("pthreads >= 3.1.5 is required, while you have $pthreads_version."); ++$errors; } if(!extension_loaded("uopz")){ //$logger->notice("Couldn't find the uopz extension. Some functions may be limited"); } if(extension_loaded("pocketmine")){ if(version_compare(phpversion("pocketmine"), "0.0.1") < 0){ $logger->critical("You have the native GenisysPro extension, but your version is lower than 0.0.1."); ++$errors; }elseif(version_compare(phpversion("pocketmine"), "0.0.4") > 0){ $logger->critical("You have the native GenisysPro extension, but your version is higher than 0.0.4."); ++$errors; } } if(extension_loaded("xdebug")){ $logger->warning("You are running GenisysPro with Xdebug enabled. This has a major impact on performance."); } if(!extension_loaded("curl")){ $logger->critical("Unable to find the cURL extension."); ++$errors; } if(!extension_loaded("yaml")){ $logger->critical("Unable to find the YAML extension."); ++$errors; } if(!extension_loaded("zlib")){ $logger->critical("Unable to find the Zlib extension."); ++$errors; } if($errors > 0){ $logger->critical("Please update or recompile PHP."); $logger->shutdown(); $logger->join(); exit(1); //Exit with error } if(file_exists(\pocketmine\PATH . ".git/HEAD")){ //Found Git information! $ref = trim(file_get_contents(\pocketmine\PATH . ".git/HEAD")); if(preg_match('/^[0-9a-f]{40}$/i', $ref)){ define('pocketmine\GIT_COMMIT', strtolower($ref)); }elseif(substr($ref, 0, 5) === "ref: "){ $refFile = \pocketmine\PATH . ".git/" . substr(trim(file_get_contents(\pocketmine\PATH . ".git/HEAD")), 5); if(is_file($refFile)){ define('pocketmine\GIT_COMMIT', strtolower(trim(file_get_contents($refFile)))); } } } if(!defined('pocketmine\GIT_COMMIT')){ define('pocketmine\GIT_COMMIT', "0000000000000000000000000000000000000000"); } @define("ENDIANNESS", (pack("d", 1) === "\77\360\0\0\0\0\0\0" ? Binary::BIG_ENDIAN : Binary::LITTLE_ENDIAN)); @define("INT32_MASK", is_int(0xffffffff) ? 0xffffffff : -1); @ini_set("opcache.mmap_base", bin2hex(random_bytes(8))); //Fix OPCache address errors if(!file_exists(\pocketmine\DATA . "server.properties") and !isset($opts["no-wizard"])){ $installer = new Installer(); if(!$installer->run()){ $logger->shutdown(); $logger->join(); exit(-1); } } if(\Phar::running(true) === ""){ $logger->warning("Non-packaged GenisysPro installation detected, do not use on production."); } ThreadManager::init(); new Server($autoloader, $logger, \pocketmine\PATH, \pocketmine\DATA, \pocketmine\PLUGIN_PATH); $logger->info("Stopping other threads"); $killer = new ServerKiller(8); $killer->start(); usleep(10000); //Fixes ServerKiller not being able to start on single-core machines $erroredThreads = 0; foreach(ThreadManager::getInstance()->getAll() as $id => $thread){ $logger->debug("Stopping " . $thread->getThreadName() . " thread"); try{ $thread->quit(); $logger->debug($thread->getThreadName() . " thread stopped successfully."); }catch(\ThreadException $e){ ++$erroredThreads; $logger->debug("Could not stop " . $thread->getThreadName() . " thread: " . $e->getMessage()); } } $logger->shutdown(); $logger->join(); echo Terminal::$FORMAT_RESET . PHP_EOL; if($erroredThreads > 0){ if(\pocketmine\DEBUG > 1){ echo "Some threads could not be stopped, performing a force-kill" . PHP_EOL . PHP_EOL; } kill(getmypid()); }else{ exit(0); } } isRunning === true; } /** * @return string * Returns a formatted string of how long the server has been running for */ public function getUptime(){ $time = microtime(true) - \pocketmine\START_TIME; $seconds = floor($time % 60); $minutes = null; $hours = null; $days = null; if($time >= 60){ $minutes = floor(($time % 3600) / 60); if($time >= 3600){ $hours = floor(($time % (3600 * 24)) / 3600); if($time >= 3600 * 24){ $days = floor($time / (3600 * 24)); } } } $uptime = ($minutes !== null ? ($hours !== null ? ($days !== null ? "$days " . $this->getLanguage()->translateString("%pocketmine.command.status.days") . " " : "") . "$hours " . $this->getLanguage()->translateString("%pocketmine.command.status.hours") . " " : "") . "$minutes " . $this->getLanguage()->translateString("%pocketmine.command.status.minutes") . " " : "") . "$seconds " . $this->getLanguage()->translateString("%pocketmine.command.status.seconds"); return $uptime; } /** * @return string */ public function getPocketMineVersion(){ return \pocketmine\VERSION; } public function getFormattedVersion($prefix = ""){ return (\pocketmine\VERSION !== ""? $prefix . \pocketmine\VERSION : ""); } /** * @return string */ public function getGitCommit(){ return \pocketmine\GIT_COMMIT; } /** * @return string */ public function getShortGitCommit(){ return substr(\pocketmine\GIT_COMMIT, 0, 7); } /** * @return string */ public function getCodename(){ return \pocketmine\CODENAME; } /** * @return string */ public function getVersion(){ $version = implode(",",ProtocolInfo::MINECRAFT_VERSION); return $version; } /** * @return string */ public function getApiVersion(){ return \pocketmine\API_VERSION; } /** * @return string */ public function getiTXApiVersion(){ return \pocketmine\GENISYS_API_VERSION; } /** * @return string */ public function getGeniApiVersion(){ return \pocketmine\GENISYS_API_VERSION; } /** * @return string */ public function getFilePath(){ return $this->filePath; } /** * @return string */ public function getDataPath(){ return $this->dataPath; } /** * @return string */ public function getPluginPath(){ return $this->pluginPath; } /** * @return int */ public function getMaxPlayers(){ return $this->maxPlayers; } /** * @return int */ public function getPort(){ return $this->getConfigInt("server-port", 19132); } /** * @return int */ public function getViewDistance() : int{ return max(2, $this->getConfigInt("view-distance", 8)); } /** * Returns a view distance up to the currently-allowed limit. * * @param int $distance * * @return int */ public function getAllowedViewDistance(int $distance) : int{ return max(2, min($distance, $this->memoryManager->getViewDistance($this->getViewDistance()))); } /** * @return string */ public function getIp(){ return $this->getConfigString("server-ip", "0.0.0.0"); } public function getServerUniqueId(){ return $this->serverID; } /** * @return bool */ public function getAutoSave(){ return $this->autoSave; } /** * @param bool $value */ public function setAutoSave($value){ $this->autoSave = (bool) $value; foreach($this->getLevels() as $level){ $level->setAutoSave($this->autoSave); } } /** * @return string */ public function getLevelType(){ return $this->getConfigString("level-type", "DEFAULT"); } /** * @return bool */ public function getGenerateStructures(){ return $this->getConfigBoolean("generate-structures", true); } /** * @return int */ public function getGamemode(){ return $this->getConfigInt("gamemode", 0) & 0b11; } /** * @return bool */ public function getForceGamemode(){ return $this->getConfigBoolean("force-gamemode", false); } /** * Returns the gamemode text name * * @param int $mode * * @return string */ public static function getGamemodeString($mode){ switch((int) $mode){ case Player::SURVIVAL: return "%gameMode.survival"; case Player::CREATIVE: return "%gameMode.creative"; case Player::ADVENTURE: return "%gameMode.adventure"; case Player::SPECTATOR: return "%gameMode.spectator"; } return "UNKNOWN"; } /** * Parses a string and returns a gamemode integer, -1 if not found * * @param string $str * * @return int */ public static function getGamemodeFromString($str){ switch(strtolower(trim($str))){ case (string) Player::SURVIVAL: case "survival": case "s": return Player::SURVIVAL; case (string) Player::CREATIVE: case "creative": case "c": return Player::CREATIVE; case (string) Player::ADVENTURE: case "adventure": case "a": return Player::ADVENTURE; case (string) Player::SPECTATOR: case "spectator": case "view": case "v": return Player::SPECTATOR; } return -1; } /** * @param string $str * * @return int */ public static function getDifficultyFromString($str){ switch(strtolower(trim($str))){ case "0": case "peaceful": case "p": return 0; case "1": case "easy": case "e": return 1; case "2": case "normal": case "n": return 2; case "3": case "hard": case "h": return 3; } return -1; } /** * @return int */ public function getDifficulty(){ return $this->getConfigInt("difficulty", 1); } /** * @return bool */ public function hasWhitelist(){ return $this->getConfigBoolean("white-list", false); } /** * @return int */ public function getSpawnRadius(){ return $this->getConfigInt("spawn-protection", 16); } /** * @return bool */ public function getAllowFlight(){ return $this->getConfigBoolean("allow-flight", false); } /** * @return bool */ public function isHardcore(){ return $this->getConfigBoolean("hardcore", false); } /** * @return int */ public function getDefaultGamemode(){ return $this->getConfigInt("gamemode", 0) & 0b11; } /** * @return string */ public function getMotd(){ return $this->getConfigString("motd", "Minecraft: PE Server"); } /** * @return \ClassLoader */ public function getLoader(){ return $this->autoloader; } /** * @return MainLogger */ public function getLogger(){ return $this->logger; } /** * @return EntityMetadataStore */ public function getEntityMetadata(){ return $this->entityMetadata; } /** * @return PlayerMetadataStore */ public function getPlayerMetadata(){ return $this->playerMetadata; } /** * @return LevelMetadataStore */ public function getLevelMetadata(){ return $this->levelMetadata; } /** * @return PluginManager */ public function getPluginManager(){ return $this->pluginManager; } /** * @return CraftingManager */ public function getCraftingManager(){ return $this->craftingManager; } /** * @return ResourcePackManager */ public function getResourceManager() : ResourcePackManager{ return $this->resourceManager; } public function getResourcePackManager() : ResourcePackManager{ return $this->resourceManager; } /** * @return ServerScheduler */ public function getScheduler(){ return $this->scheduler; } /** * @return int */ public function getTick(){ return $this->tickCounter; } /** * Returns the last server TPS measure * * @return float */ public function getTicksPerSecond(){ return round($this->maxTick, 2); } /** * Returns the last server TPS average measure * * @return float */ public function getTicksPerSecondAverage(){ return round(array_sum($this->tickAverage) / count($this->tickAverage), 2); } /** * Returns the TPS usage/load in % * * @return float */ public function getTickUsage(){ return round($this->maxUse * 100, 2); } /** * Returns the TPS usage/load average in % * * @return float */ public function getTickUsageAverage(){ return round((array_sum($this->useAverage) / count($this->useAverage)) * 100, 2); } /** * @return SimpleCommandMap */ public function getCommandMap(){ return $this->commandMap; } /** * @return Player[] */ public function getOnlinePlayers(){ return $this->playerList; } public function addRecipe(Recipe $recipe){ $this->craftingManager->registerRecipe($recipe); } public function shouldSavePlayerData() : bool{ return (bool) $this->getProperty("player.save-player-data", true); } /** * @param string $name * * @return OfflinePlayer|Player */ public function getOfflinePlayer($name){ $name = strtolower($name); $result = $this->getPlayerExact($name); if($result === null){ $result = new OfflinePlayer($this, $name); } return $result; } /** * @param string $name * * @return CompoundTag */ public function getOfflinePlayerData($name){ $name = strtolower($name); $path = $this->getDataPath() . "players/"; if($this->shouldSavePlayerData()){ if(file_exists($path . "$name.dat")){ try{ $nbt = new NBT(NBT::BIG_ENDIAN); $nbt->readCompressed(file_get_contents($path . "$name.dat")); return $nbt->getData(); }catch(\Throwable $e){ //zlib decode error / corrupt data rename($path . "$name.dat", $path . "$name.dat.bak"); $this->logger->notice($this->getLanguage()->translateString("pocketmine.data.playerCorrupted", [$name])); } }else{ $this->logger->notice($this->getLanguage()->translateString("pocketmine.data.playerNotFound", [$name])); } } $spawn = $this->getDefaultLevel()->getSafeSpawn(); $nbt = new CompoundTag("", [ new LongTag("firstPlayed", floor(microtime(true) * 1000)), new LongTag("lastPlayed", floor(microtime(true) * 1000)), new ListTag("Pos", [ new DoubleTag(0, $spawn->x), new DoubleTag(1, $spawn->y), new DoubleTag(2, $spawn->z) ]), new StringTag("Level", $this->getDefaultLevel()->getName()), //new StringTag("SpawnLevel", $this->getDefaultLevel()->getName()), //new IntTag("SpawnX", (int) $spawn->x), //new IntTag("SpawnY", (int) $spawn->y), //new IntTag("SpawnZ", (int) $spawn->z), //new ByteTag("SpawnForced", 1), //TODO new ListTag("Inventory", []), new ListTag("EnderChestInventory", []), new CompoundTag("Achievements", []), new IntTag("playerGameType", $this->getGamemode()), new ListTag("Motion", [ new DoubleTag(0, 0.0), new DoubleTag(1, 0.0), new DoubleTag(2, 0.0) ]), new ListTag("Rotation", [ new FloatTag(0, 0.0), new FloatTag(1, 0.0) ]), new FloatTag("FallDistance", 0.0), new ShortTag("Fire", 0), new ShortTag("Air", 300), new ByteTag("OnGround", 1), new ByteTag("Invulnerable", 0), new StringTag("NameTag", $name), new ShortTag("Health", 20), new ShortTag("MaxHealth", 20), ]); $nbt->Pos->setTagType(NBT::TAG_Double); $nbt->Inventory->setTagType(NBT::TAG_Compound); $nbt->EnderChestInventory->setTagType(NBT::TAG_Compound); $nbt->Motion->setTagType(NBT::TAG_Double); $nbt->Rotation->setTagType(NBT::TAG_Float); $this->saveOfflinePlayerData($name, $nbt); return $nbt; } /** * @param string $name * @param CompoundTag $nbtTag * @param bool $async */ public function saveOfflinePlayerData($name, CompoundTag $nbtTag, $async = false){ if($this->shouldSavePlayerData()){ $nbt = new NBT(NBT::BIG_ENDIAN); try{ $nbt->setData($nbtTag); if($async){ $this->getScheduler()->scheduleAsyncTask(new FileWriteTask($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed())); }else{ file_put_contents($this->getDataPath() . "players/" . strtolower($name) . ".dat", $nbt->writeCompressed()); } }catch(\Throwable $e){ $this->logger->critical($this->getLanguage()->translateString("pocketmine.data.saveError", [$name, $e->getMessage()])); $this->logger->logException($e); } } } /** * @param string $name * * @return Player */ public function getPlayer(string $name){ $found = null; $name = strtolower($name); $delta = PHP_INT_MAX; foreach($this->getOnlinePlayers() as $player){ if(stripos($player->getName(), $name) === 0){ $curDelta = strlen($player->getName()) - strlen($name); if($curDelta < $delta){ $found = $player; $delta = $curDelta; } if($curDelta === 0){ break; } } } return $found; } /** * @param string $name * * @return Player */ public function getPlayerExact(string $name){ $name = strtolower($name); foreach($this->getOnlinePlayers() as $player){ if(strtolower($player->getName()) === $name){ return $player; } } return null; } /** * @param string $partialName * * @return Player[] */ public function matchPlayer($partialName){ $partialName = strtolower($partialName); $matchedPlayers = []; foreach($this->getOnlinePlayers() as $player){ if(strtolower($player->getName()) === $partialName){ $matchedPlayers = [$player]; break; }elseif(stripos($player->getName(), $partialName) !== false){ $matchedPlayers[] = $player; } } return $matchedPlayers; } /** * @param Player $player */ public function removePlayer(Player $player){ if(isset($this->identifiers[$hash = spl_object_hash($player)])){ $identifier = $this->identifiers[$hash]; unset($this->players[$identifier]); unset($this->identifiers[$hash]); return; } foreach($this->players as $identifier => $p){ if($player === $p){ unset($this->players[$identifier]); unset($this->identifiers[spl_object_hash($player)]); break; } } } /** * @return Level[] */ public function getLevels(){ return $this->levels; } /** * @return Level */ public function getDefaultLevel(){ return $this->levelDefault; } /** * Sets the default level to a different level * This won't change the level-name property, * it only affects the server on runtime * * @param Level $level */ public function setDefaultLevel($level){ if($level === null or ($this->isLevelLoaded($level->getFolderName()) and $level !== $this->levelDefault)){ $this->levelDefault = $level; } } /** * @param string $name * * @return bool */ public function isLevelLoaded($name){ return $this->getLevelByName($name) instanceof Level; } /** * @param int $levelId * * @return Level */ public function getLevel($levelId){ if(isset($this->levels[$levelId])){ return $this->levels[$levelId]; } return null; } /** * @param $name * * @return Level */ public function getLevelByName($name){ foreach($this->getLevels() as $level){ if($level->getFolderName() === $name){ return $level; } } return null; } public function getExpectedExperience($level){ if(isset($this->expCache[$level])) return $this->expCache[$level]; $levelSquared = $level ** 2; if($level < 16) $this->expCache[$level] = $levelSquared + 6 * $level; elseif($level < 31) $this->expCache[$level] = 2.5 * $levelSquared - 40.5 * $level + 360; else $this->expCache[$level] = 4.5 * $levelSquared - 162.5 * $level + 2220; return $this->expCache[$level]; } /** * @param Level $level * @param bool $forceUnload * * @return bool */ public function unloadLevel(Level $level, $forceUnload = false){ if($level === $this->getDefaultLevel() and !$forceUnload){ throw new \InvalidStateException("The default level cannot be unloaded while running, please switch levels."); } if($level->unload($forceUnload) === true){ unset($this->levels[$level->getId()]); return true; } return false; } /** * Loads a level from the data directory * * @param string $name * * @return bool * * @throws LevelException */ public function loadLevel($name){ if(trim($name) === ""){ throw new LevelException("Invalid empty level name"); } if($this->isLevelLoaded($name)){ return true; }elseif(!$this->isLevelGenerated($name)){ $this->logger->notice($this->getLanguage()->translateString("pocketmine.level.notFound", [$name])); return false; } $path = $this->getDataPath() . "worlds/" . $name . "/"; $provider = LevelProviderManager::getProvider($path); if($provider === null){ $this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, "Unknown provider"])); return false; } try{ $level = new Level($this, $name, $path, $provider); }catch(\Throwable $e){ $this->logger->error($this->getLanguage()->translateString("pocketmine.level.loadError", [$name, $e->getMessage()])); if($this->logger instanceof MainLogger){ $this->logger->logException($e); } return false; } $this->levels[$level->getId()] = $level; $level->initLevel(); $this->getPluginManager()->callEvent(new LevelLoadEvent($level)); $level->setTickRate($this->baseTickRate); return true; } /** * Generates a new level if it does not exists * * @param string $name * @param int $seed * @param string $generator Class name that extends pocketmine\level\generator\Noise * @param array $options * * @return bool */ public function generateLevel($name, $seed = null, $generator = null, $options = []){ if(trim($name) === "" or $this->isLevelGenerated($name)){ return false; } $seed = $seed === null ? Binary::readInt(random_bytes(4)) : (int) $seed; if(!isset($options["preset"])){ $options["preset"] = $this->getConfigString("generator-settings", ""); } if(!($generator !== null and class_exists($generator, true) and is_subclass_of($generator, Generator::class))){ $generator = Generator::getGenerator($this->getLevelType()); } if(($provider = LevelProviderManager::getProviderByName($providerName = $this->getProperty("level-settings.default-format", "pmanvil"))) === null){ $provider = LevelProviderManager::getProviderByName($providerName = "pmanvil"); } try{ $path = $this->getDataPath() . "worlds/" . $name . "/"; /** @var \pocketmine\level\format\io\LevelProvider $provider */ $provider::generate($path, $name, $seed, $generator, $options); $level = new Level($this, $name, $path, $provider); $this->levels[$level->getId()] = $level; $level->initLevel(); $level->setTickRate($this->baseTickRate); }catch(\Throwable $e){ $this->logger->error($this->getLanguage()->translateString("pocketmine.level.generateError", [$name, $e->getMessage()])); if($this->logger instanceof MainLogger){ $this->logger->logException($e); } return false; } $this->getPluginManager()->callEvent(new LevelInitEvent($level)); $this->getPluginManager()->callEvent(new LevelLoadEvent($level)); $this->getLogger()->notice($this->getLanguage()->translateString("pocketmine.level.backgroundGeneration", [$name])); $centerX = $level->getSpawnLocation()->getX() >> 4; $centerZ = $level->getSpawnLocation()->getZ() >> 4; $order = []; for($X = -3; $X <= 3; ++$X){ for($Z = -3; $Z <= 3; ++$Z){ $distance = $X ** 2 + $Z ** 2; $chunkX = $X + $centerX; $chunkZ = $Z + $centerZ; $index = Level::chunkHash($chunkX, $chunkZ); $order[$index] = $distance; } } asort($order); foreach($order as $index => $distance){ Level::getXZ($index, $chunkX, $chunkZ); $level->populateChunk($chunkX, $chunkZ, true); } return true; } /** * @param string $name * * @return bool */ public function isLevelGenerated($name){ if(trim($name) === ""){ return false; } $path = $this->getDataPath() . "worlds/" . $name . "/"; if(!($this->getLevelByName($name) instanceof Level)){ if(LevelProviderManager::getProvider($path) === null){ return false; } /*if(file_exists($path)){ $level = new LevelImport($path); if($level->import() === false){ //Try importing a world return false; } }else{ return false; }*/ } return true; } /** * Searches all levels for the entity with the specified ID. * Useful for tracking entities across multiple worlds without needing strong references. * * @param int $entityId * @param Level|null $expectedLevel Level to look in first for the target * * @return Entity|null */ public function findEntity(int $entityId, Level $expectedLevel = null){ $levels = $this->levels; if($expectedLevel !== null){ array_unshift($levels, $expectedLevel); } foreach($levels as $level){ assert(!$level->isClosed()); if(($entity = $level->getEntity($entityId)) instanceof Entity){ return $entity; } } return null; } /** * @param string $variable * @param string $defaultValue * * @return string */ public function getConfigString($variable, $defaultValue = ""){ $v = getopt("", ["$variable::"]); if(isset($v[$variable])){ return (string) $v[$variable]; } return $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue; } /** * @param string $variable * @param mixed $defaultValue * * @return mixed */ public function getProperty($variable, $defaultValue = null){ if(!array_key_exists($variable, $this->propertyCache)){ $v = getopt("", ["$variable::"]); if(isset($v[$variable])){ $this->propertyCache[$variable] = $v[$variable]; }else{ $this->propertyCache[$variable] = $this->config->getNested($variable); } } return $this->propertyCache[$variable] === null ? $defaultValue : $this->propertyCache[$variable]; } /** * @param string $variable * @param string $value */ public function setConfigString($variable, $value){ $this->properties->set($variable, $value); } /** * @param string $variable * @param int $defaultValue * * @return int */ public function getConfigInt($variable, $defaultValue = 0){ $v = getopt("", ["$variable::"]); if(isset($v[$variable])){ return (int) $v[$variable]; } return $this->properties->exists($variable) ? (int) $this->properties->get($variable) : (int) $defaultValue; } /** * @param string $variable * @param int $value */ public function setConfigInt($variable, $value){ $this->properties->set($variable, (int) $value); } /** * @param string $variable * @param boolean $defaultValue * * @return boolean */ public function getConfigBoolean($variable, $defaultValue = false){ $v = getopt("", ["$variable::"]); if(isset($v[$variable])){ $value = $v[$variable]; }else{ $value = $this->properties->exists($variable) ? $this->properties->get($variable) : $defaultValue; } if(is_bool($value)){ return $value; } switch(strtolower($value)){ case "on": case "true": case "1": case "yes": return true; } return false; } /** * @param string $variable * @param bool $value */ public function setConfigBool($variable, $value){ $this->properties->set($variable, $value == true ? "1" : "0"); } /** * @param string $name * * @return command\Command */ public function getPluginCommand($name){ if(($command = $this->commandMap->getCommand($name)) instanceof PluginIdentifiableCommand){ return $command; }else{ return null; } } /** * @return BanList */ public function getNameBans(){ return $this->banByName; } /** * @return BanList */ public function getIPBans(){ return $this->banByIP; } public function getCIDBans(){ return $this->banByCID; } /** * @param string $name */ public function addOp($name){ $this->operators->set(strtolower($name), true); if(($player = $this->getPlayerExact($name)) !== null){ $player->recalculatePermissions(); } $this->operators->save(true); } /** * @param string $name */ public function removeOp($name){ foreach($this->operators->getAll() as $opName => $dummyValue){ if(strtolower($name) === strtolower($opName)){ $this->operators->remove($opName); } } if(($player = $this->getPlayerExact($name)) !== null){ $player->recalculatePermissions(); } $this->operators->save(); } /** * @param string $name */ public function addWhitelist($name){ $this->whitelist->set(strtolower($name), true); $this->whitelist->save(true); } /** * @param string $name */ public function removeWhitelist($name){ $this->whitelist->remove(strtolower($name)); $this->whitelist->save(); } /** * @param string $name * * @return bool */ public function isWhitelisted($name){ return !$this->hasWhitelist() or $this->whitelist->exists($name, true); } /** * @param string $name * * @return bool */ public function isOp($name){ return $this->operators->exists($name, true); } /** * @return Config */ public function getWhitelisted(){ return $this->whitelist; } /** * @return Config */ public function getOps(){ return $this->operators; } public function reloadWhitelist(){ $this->whitelist->reload(); } /** * @return string[] */ public function getCommandAliases(){ $section = $this->getProperty("aliases"); $result = []; if(is_array($section)){ foreach($section as $key => $value){ $commands = []; if(is_array($value)){ $commands = $value; }else{ $commands[] = $value; } $result[$key] = $commands; } } return $result; } public function getCrashPath(){ return $this->dataPath . "crashdumps/"; } /** * @return Server */ public static function getInstance() : Server{ return self::$instance; } public static function microSleep(int $microseconds){ Server::$sleeper->synchronized(function(int $ms){ Server::$sleeper->wait($ms); }, $microseconds); } public function about(){ $version = implode(",",ProtocolInfo::MINECRAFT_VERSION); $string = " _____ _ _____ / ____| (_) | __ \ | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ __/ | |___/ Version: §6" . $this->getPocketMineVersion() . ' (' . $this->getShortGitCommit() . ')§f Client Version: §b' . $version . '§f PHP Version: §e' . PHP_VERSION . '§f OS: §6' . PHP_OS .'§f This core is maintained by §dGenisysPro§f (https://github.com/GenisysPro) Discord Group chat: §ehttps://discord.gg/WrKzRNn §f Chatroom on QQ: §a559301590 §f Welcome to donate us on QQ: §c1912003473 '; $this->getLogger()->info($string); } public function loadAdvancedConfig(){ $this->playerMsgType = $this->getAdvancedProperty("server.player-msg-type", self::PLAYER_MSG_TYPE_MESSAGE); $this->playerLoginMsg = $this->getAdvancedProperty("server.login-msg", "§3@player joined the game"); $this->playerLogoutMsg = $this->getAdvancedProperty("server.logout-msg", "§3@player left the game"); $this->weatherEnabled = $this->getAdvancedProperty("level.weather", true); $this->foodEnabled = $this->getAdvancedProperty("player.hunger", true); $this->expEnabled = $this->getAdvancedProperty("player.experience", true); $this->keepInventory = $this->getAdvancedProperty("player.keep-inventory", false); $this->keepExperience = $this->getAdvancedProperty("player.keep-experience", false); $this->loadIncompatibleAPI = $this->getAdvancedProperty("developer.load-incompatible-api", true); $this->netherEnabled = $this->getAdvancedProperty("nether.allow-nether", false); $this->netherName = $this->getAdvancedProperty("nether.level-name", "nether"); $this->enderEnabled = $this->getAdvancedProperty("ender.allow-ender", false); $this->enderName = $this->getAdvancedProperty("ender.level-name", "ender"); $this->weatherRandomDurationMin = $this->getAdvancedProperty("level.weather-random-duration-min", 6000); $this->weatherRandomDurationMax = $this->getAdvancedProperty("level.weather-random-duration-max", 12000); $this->lightningTime = $this->getAdvancedProperty("level.lightning-time", 200); $this->lightningFire = $this->getAdvancedProperty("level.lightning-fire", false); $this->allowSnowGolem = $this->getAdvancedProperty("server.allow-snow-golem", false); $this->allowIronGolem = $this->getAdvancedProperty("server.allow-iron-golem", false); $this->autoClearInv = $this->getAdvancedProperty("player.auto-clear-inventory", true); $this->dserverConfig = [ "enable" => $this->getAdvancedProperty("dserver.enable", false), "queryAutoUpdate" => $this->getAdvancedProperty("dserver.query-auto-update", false), "queryTickUpdate" => $this->getAdvancedProperty("dserver.query-tick-update", true), "motdMaxPlayers" => $this->getAdvancedProperty("dserver.motd-max-players", 0), "queryMaxPlayers" => $this->getAdvancedProperty("dserver.query-max-players", 0), "motdAllPlayers" => $this->getAdvancedProperty("dserver.motd-all-players", false), "queryAllPlayers" => $this->getAdvancedProperty("dserver.query-all-players", false), "motdPlayers" => $this->getAdvancedProperty("dserver.motd-players", false), "queryPlayers" => $this->getAdvancedProperty("dserver.query-players", false), "timer" => $this->getAdvancedProperty("dserver.time", 40), "retryTimes" => $this->getAdvancedProperty("dserver.retry-times", 3), "serverList" => explode(";", $this->getAdvancedProperty("dserver.server-list", "")) ]; $this->redstoneEnabled = $this->getAdvancedProperty("redstone.enable", false); $this->allowFrequencyPulse = $this->getAdvancedProperty("redstone.allow-frequency-pulse", false); $this->pulseFrequency = $this->getAdvancedProperty("redstone.pulse-frequency", 20); $this->getLogger()->setWrite(!$this->getAdvancedProperty("server.disable-log", false)); $this->limitedCreative = $this->getAdvancedProperty("server.limited-creative", true); $this->chunkRadius = $this->getAdvancedProperty("player.chunk-radius", -1); $this->destroyBlockParticle = $this->getAdvancedProperty("server.destroy-block-particle", true); $this->allowSplashPotion = $this->getAdvancedProperty("server.allow-splash-potion", true); $this->fireSpread = $this->getAdvancedProperty("level.fire-spread", false); $this->advancedCommandSelector = $this->getAdvancedProperty("server.advanced-command-selector", false); $this->anvilEnabled = $this->getAdvancedProperty("enchantment.enable-anvil", true); $this->enchantingTableEnabled = $this->getAdvancedProperty("enchantment.enable-enchanting-table", true); $this->countBookshelf = $this->getAdvancedProperty("enchantment.count-bookshelf", false); $this->allowInventoryCheats = $this->getAdvancedProperty("inventory.allow-cheats", false); $this->folderpluginloader = $this->getAdvancedProperty("developer.folder-plugin-loader", true); $this->absorbWater = $this->getAdvancedProperty("server.absorb-water", false); } /** * @return int * * Get DServer max players */ public function getDServerMaxPlayers(){ return ($this->dserverAllPlayers + $this->getMaxPlayers()); } /** * @return int * * Get DServer all online player count */ public function getDServerOnlinePlayers(){ return ($this->dserverPlayers + count($this->getOnlinePlayers())); } public function isDServerEnabled(){ return $this->dserverConfig["enable"]; } public function updateDServerInfo(){ $this->scheduler->scheduleAsyncTask(new DServerTask($this->dserverConfig["serverList"], $this->dserverConfig["retryTimes"])); } public function getBuild(){ return $this->version->getBuild(); } public function getGameVersion(){ return $this->version->getRelease(); } /** * @param \ClassLoader $autoloader * @param \ThreadedLogger $logger * @param string $filePath * @param string $dataPath * @param string $pluginPath * @param string $defaultLang */ public function __construct(\ClassLoader $autoloader, \ThreadedLogger $logger, $filePath, $dataPath, $pluginPath, $defaultLang = "unknown"){ self::$instance = $this; self::$sleeper = new \Threaded; $this->autoloader = $autoloader; $this->logger = $logger; $this->filePath = $filePath; try{ if(!file_exists($dataPath . "worlds/")){ mkdir($dataPath . "worlds/", 0777); } if(!file_exists($dataPath . "players/")){ mkdir($dataPath . "players/", 0777); } if(!file_exists($pluginPath)){ mkdir($pluginPath, 0777); } if(!file_exists($dataPath . "crashdumps/")){ mkdir($dataPath . "crashdumps/", 0777); } $this->dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR; $this->pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR; $this->console = new CommandReader(); $version = new VersionString($this->getPocketMineVersion()); $this->version = $version; $this->about(); $this->logger->info("Loading properties and configuration..."); if(!file_exists($this->dataPath . "pocketmine.yml")){ if(file_exists($this->dataPath . "lang.txt")){ $langFile = new Config($configPath = $this->dataPath . "lang.txt", Config::ENUM, []); $wizardLang = null; foreach ($langFile->getAll(true) as $langName) { $wizardLang = $langName; break; } if(file_exists($this->filePath . "src/pocketmine/resources/pocketmine_$wizardLang.yml")){ $content = file_get_contents($file = $this->filePath . "src/pocketmine/resources/pocketmine_$wizardLang.yml"); }else{ $content = file_get_contents($file = $this->filePath . "src/pocketmine/resources/pocketmine_eng.yml"); } }else{ $content = file_get_contents($file = $this->filePath . "src/pocketmine/resources/pocketmine_eng.yml"); } @file_put_contents($this->dataPath . "pocketmine.yml", $content); } if(file_exists($this->dataPath . "lang.txt")){ unlink($this->dataPath . "lang.txt"); } $this->config = new Config($configPath = $this->dataPath . "pocketmine.yml", Config::YAML, []); $nowLang = $this->getProperty("settings.language", "eng"); //Crashes unsupported builds without the correct configuration if(strpos(\pocketmine\VERSION, "unsupported") !== false and getenv("CI") === false){ if($this->getProperty("settings.enable-testing", false) !== true){ throw new ServerException("This build is not intended for production use. You may set 'settings.enable-testing: true' under pocketmine.yml to allow use of non-production builds. Do so at your own risk and ONLY if you know what you are doing."); }else{ $this->logger->warning("You are using an unsupported build. Do not use this build in a production environment."); } } if($defaultLang != "unknown" and $nowLang != $defaultLang){ @file_put_contents($configPath, str_replace('language: "' . $nowLang . '"', 'language: "' . $defaultLang . '"', file_get_contents($configPath))); $this->config->reload(); unset($this->propertyCache["settings.language"]); } $lang = $this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE); if(file_exists($this->filePath . "src/pocketmine/resources/genisys_$lang.yml")){ $content = file_get_contents($file = $this->filePath . "src/pocketmine/resources/genisys_$lang.yml"); }else{ $content = file_get_contents($file = $this->filePath . "src/pocketmine/resources/genisys_eng.yml"); } if(!file_exists($this->dataPath . "genisys.yml")){ @file_put_contents($this->dataPath . "genisys.yml", $content); } $internelConfig = new Config($file, Config::YAML, []); $this->advancedConfig = new Config($this->dataPath . "genisys.yml", Config::YAML, []); $cfgVer = $this->getAdvancedProperty("config.version", 0, $internelConfig); $advVer = $this->getAdvancedProperty("config.version", 0); $this->loadAdvancedConfig(); $this->properties = new Config($this->dataPath . "server.properties", Config::PROPERTIES, [ "motd" => "Minecraft: PE Server", "server-port" => 19132, "white-list" => false, "announce-player-achievements" => true, "spawn-protection" => 16, "max-players" => 20, "allow-flight" => false, "spawn-animals" => true, "spawn-mobs" => true, "gamemode" => 0, "force-gamemode" => false, "hardcore" => false, "pvp" => true, "difficulty" => 1, "generator-settings" => "", "level-name" => "world", "level-seed" => "", "level-type" => "DEFAULT", "enable-query" => true, "enable-rcon" => false, "rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10), "auto-save" => true, "online-mode" => false, "view-distance" => 8 ]); $onlineMode = $this->getConfigBoolean("online-mode", false); if(!extension_loaded("openssl")){ $this->logger->warning("OpenSSL extension not found"); $this->logger->warning("Please configure OpenSSL extension for PHP if you want to use Xbox Live authentication or global resource pack."); $this->setConfigBool("online-mode", false); }elseif(!$onlineMode){ $this->logger->warning("Online mode has been turned off in server.properties"); $this->logger->warning("Xbox Live authentication is disabled."); } $this->forceLanguage = $this->getProperty("settings.force-language", false); $this->baseLang = new BaseLang($this->getProperty("settings.language", BaseLang::FALLBACK_LANGUAGE)); $this->logger->info($this->getLanguage()->translateString("language.selected", [$this->getLanguage()->getName(), $this->getLanguage()->getLang()])); $this->memoryManager = new MemoryManager($this); if(($poolSize = $this->getProperty("settings.async-workers", "auto")) === "auto"){ $poolSize = ServerScheduler::$WORKERS; $processors = Utils::getCoreCount() - 2; if($processors > 0){ $poolSize = max(1, $processors); } } ServerScheduler::$WORKERS = $poolSize; if($this->getProperty("network.batch-threshold", 256) >= 0){ Network::$BATCH_THRESHOLD = (int) $this->getProperty("network.batch-threshold", 256); }else{ Network::$BATCH_THRESHOLD = -1; } $this->networkCompressionLevel = $this->getProperty("network.compression-level", 7); $this->networkCompressionAsync = $this->getProperty("network.async-compression", true); $this->autoTickRate = (bool) $this->getProperty("level-settings.auto-tick-rate", true); $this->autoTickRateLimit = (int) $this->getProperty("level-settings.auto-tick-rate-limit", 20); $this->alwaysTickPlayers = (int) $this->getProperty("level-settings.always-tick-players", false); $this->baseTickRate = (int) $this->getProperty("level-settings.base-tick-rate", 1); $this->scheduler = new ServerScheduler(); if($this->getConfigBoolean("enable-rcon", false) === true){ $this->rcon = new RCON($this, $this->getConfigString("rcon.password", ""), $this->getConfigInt("rcon.port", $this->getPort()), ($ip = $this->getIp()) != "" ? $ip : "0.0.0.0", $this->getConfigInt("rcon.threads", 1), $this->getConfigInt("rcon.clients-per-thread", 50)); } $this->entityMetadata = new EntityMetadataStore(); $this->playerMetadata = new PlayerMetadataStore(); $this->levelMetadata = new LevelMetadataStore(); $this->operators = new Config($this->dataPath . "ops.txt", Config::ENUM); $this->whitelist = new Config($this->dataPath . "white-list.txt", Config::ENUM); if(file_exists($this->dataPath . "banned.txt") and !file_exists($this->dataPath . "banned-players.txt")){ @rename($this->dataPath . "banned.txt", $this->dataPath . "banned-players.txt"); } @touch($this->dataPath . "banned-players.txt"); $this->banByName = new BanList($this->dataPath . "banned-players.txt"); $this->banByName->load(); @touch($this->dataPath . "banned-ips.txt"); $this->banByIP = new BanList($this->dataPath . "banned-ips.txt"); $this->banByIP->load(); @touch($this->dataPath . "banned-cids.txt"); $this->banByCID = new BanList($this->dataPath . "banned-cids.txt"); $this->banByCID->load(); $this->maxPlayers = $this->getConfigInt("max-players", 20); $this->setAutoSave($this->getConfigBoolean("auto-save", true)); if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){ $this->setConfigInt("difficulty", 3); } define('pocketmine\DEBUG', (int) $this->getProperty("debug.level", 1)); if(((int) ini_get('zend.assertions')) > 0 and ((bool) $this->getProperty("debug.assertions.warn-if-enabled", true)) !== false){ $this->logger->warning("Debugging assertions are enabled, this may impact on performance. To disable them, set `zend.assertions = -1` in php.ini."); } ini_set('assert.exception', (bool) $this->getProperty("debug.assertions.throw-exception", 0)); if($this->logger instanceof MainLogger){ $this->logger->setLogDebug(\pocketmine\DEBUG > 1); } if(\pocketmine\DEBUG >= 0){ @cli_set_process_title($this->getName() . " " . $this->getPocketMineVersion()); } $this->logger->info($this->getLanguage()->translateString("pocketmine.server.networkStart", [$this->getIp() === "" ? "*" : $this->getIp(), $this->getPort()])); $this->serverID = Utils::getMachineUniqueId($this->getIp() . $this->getPort()); $this->getLogger()->debug("Server unique id: " . $this->getServerUniqueId()); $this->getLogger()->debug("Machine unique id: " . Utils::getMachineUniqueId()); $this->network = new Network($this); $this->network->setName($this->getMotd()); $this->logger->info($this->getLanguage()->translateString("pocketmine.server.license", [$this->getName()])); Timings::init(); $this->consoleSender = new ConsoleCommandSender(); $this->commandMap = new SimpleCommandMap($this); Entity::init(); Tile::init(); InventoryType::init(); Block::init(); Enchantment::init(); Item::init(); Biome::init(); Effect::init(); Attribute::init(); EnchantmentLevelTable::init(); Color::init(); $this->craftingManager = new CraftingManager(); $this->resourceManager = new ResourcePackManager($this, $this->getDataPath() . "resource_packs" . DIRECTORY_SEPARATOR); $this->pluginManager = new PluginManager($this, $this->commandMap); $this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender); $this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", false)); $this->profilingTickRate = (float) $this->getProperty("settings.profile-report-trigger", 20); $this->pluginManager->registerInterface(PharPluginLoader::class); if($this->folderpluginloader === true) { $this->pluginManager->registerInterface(FolderPluginLoader::class); } $this->pluginManager->registerInterface(ScriptPluginLoader::class); //set_exception_handler([$this, "exceptionHandler"]); register_shutdown_function([$this, "crashDump"]); $this->queryRegenerateTask = new QueryRegenerateEvent($this, 5); $this->network->registerInterface(new RakLibInterface($this)); $this->pluginManager->loadPlugins($this->pluginPath); $this->enablePlugins(PluginLoadOrder::STARTUP); LevelProviderManager::addProvider(Anvil::class); LevelProviderManager::addProvider(McRegion::class); LevelProviderManager::addProvider(PMAnvil::class); if(extension_loaded("leveldb")){ $this->logger->debug($this->getLanguage()->translateString("pocketmine.debug.enable")); LevelProviderManager::addProvider(LevelDB::class); } Generator::addGenerator(Flat::class, "flat"); Generator::addGenerator(Normal::class, "normal"); Generator::addGenerator(Normal::class, "default"); Generator::addGenerator(Nether::class, "hell"); Generator::addGenerator(Nether::class, "nether"); Generator::addGenerator(VoidGenerator::class, "void"); Generator::addGenerator(Normal2::class, "normal2"); Generator::addGenerator(Ender::class, "ender"); foreach((array) $this->getProperty("worlds", []) as $name => $worldSetting){ if($this->loadLevel($name) === false){ $seed = $this->getProperty("worlds.$name.seed", time()); $options = explode(":", $this->getProperty("worlds.$name.generator", Generator::getGenerator("default"))); $generator = Generator::getGenerator(array_shift($options)); if(count($options) > 0){ $options = [ "preset" => implode(":", $options), ]; }else{ $options = []; } $this->generateLevel($name, $seed, $generator, $options); } } if($this->getDefaultLevel() === null){ $default = $this->getConfigString("level-name", "world"); if(trim($default) == ""){ $this->getLogger()->warning("level-name cannot be null, using default"); $default = "world"; $this->setConfigString("level-name", "world"); } if($this->loadLevel($default) === false){ $seed = getopt("", ["level-seed::"])["level-seed"] ?? $this->properties->get("level-seed", time()); if(!is_numeric($seed) or bccomp($seed, "9223372036854775807") > 0){ $seed = Utils::javaStringHash($seed); }elseif(PHP_INT_SIZE === 8){ $seed = (int) $seed; } $this->generateLevel($default, $seed === 0 ? time() : $seed); } $this->setDefaultLevel($this->getLevelByName($default)); } $this->properties->save(true); if(!($this->getDefaultLevel() instanceof Level)){ $this->getLogger()->emergency($this->getLanguage()->translateString("pocketmine.level.defaultError")); $this->forceShutdown(); return; } if($this->netherEnabled){ if(!$this->loadLevel($this->netherName)){ $this->generateLevel($this->netherName, time(), Generator::getGenerator("nether")); } $this->netherLevel = $this->getLevelByName($this->netherName); } if($this->enderEnabled){ if(!$this->loadLevel($this->enderName)){ $this->generateLevel($this->enderName, time(), Generator::getGenerator("ender")); } $this->enderLevel = $this->getLevelByName($this->enderName); } if($this->getProperty("ticks-per.autosave", 6000) > 0){ $this->autoSaveTicks = (int) $this->getProperty("ticks-per.autosave", 6000); } $this->enablePlugins(PluginLoadOrder::POSTWORLD); if($this->dserverConfig["enable"] and ($this->getAdvancedProperty("dserver.server-list", "") != "")) $this->scheduler->scheduleRepeatingTask(new CallbackTask([ $this, "updateDServerInfo" ]), $this->dserverConfig["timer"]); if($cfgVer > $advVer){ $this->logger->notice("Your genisys.yml needs update"); $this->logger->notice("Current Version: $advVer Latest Version: $cfgVer"); } $this->start(); }catch(\Throwable $e){ $this->exceptionHandler($e); } } /** * @param string $message * @param Player[]|null $recipients * * @return int */ public function broadcastMessage($message, $recipients = null) : int{ if(!is_array($recipients)){ return $this->broadcast($message, self::BROADCAST_CHANNEL_USERS); } /** @var Player[] $recipients */ foreach($recipients as $recipient){ $recipient->sendMessage($message); } return count($recipients); } /** * @param string $tip * @param Player[]|null $recipients * * @return int */ public function broadcastTip(string $tip, $recipients = null) : int{ if(!is_array($recipients)){ /** @var Player[] $recipients */ $recipients = []; foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){ if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){ $recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated } } } /** @var Player[] $recipients */ foreach($recipients as $recipient){ $recipient->sendTip($tip); } return count($recipients); } /** * @param string $popup * @param Player[]|null $recipients * * @return int */ public function broadcastPopup(string $popup, $recipients = null) : int{ if(!is_array($recipients)){ /** @var Player[] $recipients */ $recipients = []; foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){ if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){ $recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated } } } /** @var Player[] $recipients */ foreach($recipients as $recipient){ $recipient->sendPopup($popup); } return count($recipients); } /** * @param string $message * @param string $permissions * * @return int */ public function broadcast($message, string $permissions) : int{ /** @var CommandSender[] $recipients */ $recipients = []; foreach(explode(";", $permissions) as $permission){ foreach($this->pluginManager->getPermissionSubscriptions($permission) as $permissible){ if($permissible instanceof CommandSender and $permissible->hasPermission($permission)){ $recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated } } } foreach($recipients as $recipient){ $recipient->sendMessage($message); } return count($recipients); } /** * @param string $title * @param string $subtitle * @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used. * @param int $stay Duration in ticks to stay on screen for * @param int $fadeOut Duration in ticks for fade-out. * @param Player[]|null $recipients * * @return int */ public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1, array $recipients = null) : int{ if(!is_array($recipients)){ /** @var Player[] $recipients */ $recipients = []; foreach($this->pluginManager->getPermissionSubscriptions(self::BROADCAST_CHANNEL_USERS) as $permissible){ if($permissible instanceof Player and $permissible->hasPermission(self::BROADCAST_CHANNEL_USERS)){ $recipients[spl_object_hash($permissible)] = $permissible; // do not send messages directly, or some might be repeated } } } /** @var Player[] $recipients */ foreach($recipients as $recipient){ $recipient->addTitle($title, $subtitle, $fadeIn, $stay, $fadeOut); } return count($recipients); } /** * Broadcasts a Minecraft packet to a list of players * * @param Player[] $players * @param DataPacket $packet */ public function broadcastPacket(array $players, DataPacket $packet){ $packet->encode(); $packet->isEncoded = true; if(Network::$BATCH_THRESHOLD >= 0 and strlen($packet->buffer) >= Network::$BATCH_THRESHOLD){ $this->batchPackets($players, [$packet->buffer], false); return; } foreach($players as $player){ $player->dataPacket($packet); } if(isset($packet->__encapsulatedPacket)){ unset($packet->__encapsulatedPacket); } } /** * Broadcasts a list of packets in a batch to a list of players * * @param Player[] $players * @param DataPacket[]|string $packets * @param bool $forceSync */ public function batchPackets(array $players, array $packets, $forceSync = false){ Timings::$playerNetworkTimer->startTiming(); $str = ""; foreach($packets as $p){ if($p instanceof DataPacket){ if(!$p->isEncoded){ $p->encode(); } $str .= Binary::writeUnsignedVarInt(strlen($p->buffer)) . $p->buffer; }else{ $str .= Binary::writeUnsignedVarInt(strlen($p)) . $p; } } $targets = []; foreach($players as $p){ if($p->isConnected()){ $targets[] = $this->identifiers[spl_object_hash($p)]; } } if(!$forceSync and $this->networkCompressionAsync){ $task = new CompressBatchedTask($str, $targets, $this->networkCompressionLevel); $this->getScheduler()->scheduleAsyncTask($task); }else{ $this->broadcastPacketsCallback(zlib_encode($str, ZLIB_ENCODING_DEFLATE, $this->networkCompressionLevel), $targets); } Timings::$playerNetworkTimer->stopTiming(); } public function broadcastPacketsCallback($data, array $identifiers){ $pk = new BatchPacket(); $pk->payload = $data; $pk->encode(); $pk->isEncoded = true; foreach($identifiers as $i){ if(isset($this->players[$i])){ $this->players[$i]->dataPacket($pk); } } } /** * @param int $type */ public function enablePlugins(int $type){ foreach($this->pluginManager->getPlugins() as $plugin){ if(!$plugin->isEnabled() and $plugin->getDescription()->getOrder() === $type){ $this->enablePlugin($plugin); } } if($type === PluginLoadOrder::POSTWORLD){ $this->commandMap->registerServerAliases(); DefaultPermissions::registerCorePermissions(); } } /** * @param Plugin $plugin */ public function enablePlugin(Plugin $plugin){ $this->pluginManager->enablePlugin($plugin); } public function disablePlugins(){ $this->pluginManager->disablePlugins(); } public function checkConsole(){ Timings::$serverCommandTimer->startTiming(); if(($line = $this->console->getLine()) !== null){ $this->pluginManager->callEvent($ev = new ServerCommandEvent($this->consoleSender, $line)); if(!$ev->isCancelled()){ $this->dispatchCommand($ev->getSender(), $ev->getCommand()); } } Timings::$serverCommandTimer->stopTiming(); } /** * Executes a command from a CommandSender * * @param CommandSender $sender * @param string $commandLine * * @return bool */ public function dispatchCommand(CommandSender $sender, $commandLine){ if($this->commandMap->dispatch($sender, $commandLine)){ return true; } $sender->sendMessage(new TranslationContainer(TextFormat::GOLD . "%commands.generic.notFound")); return false; } public function reload(){ $this->logger->info("Saving levels..."); foreach($this->levels as $level){ $level->save(); } $this->pluginManager->disablePlugins(); $this->pluginManager->clearPlugins(); $this->commandMap->clearCommands(); $this->logger->info("Reloading properties..."); $this->properties->reload(); $this->advancedConfig->reload(); $this->loadAdvancedConfig(); $this->maxPlayers = $this->getConfigInt("max-players", 20); if($this->getConfigBoolean("hardcore", false) === true and $this->getDifficulty() < 3){ $this->setConfigInt("difficulty", 3); } $this->banByIP->load(); $this->banByName->load(); $this->banByCID->load(); $this->reloadWhitelist(); $this->operators->reload(); $this->memoryManager->doObjectCleanup(); foreach($this->getIPBans()->getEntries() as $entry){ $this->getNetwork()->blockAddress($entry->getName(), -1); } $this->pluginManager->registerInterface(PharPluginLoader::class); if($this->folderpluginloader === true) { $this->pluginManager->registerInterface(FolderPluginLoader::class); } $this->pluginManager->registerInterface(ScriptPluginLoader::class); $this->pluginManager->loadPlugins($this->pluginPath); $this->enablePlugins(PluginLoadOrder::STARTUP); $this->enablePlugins(PluginLoadOrder::POSTWORLD); TimingsHandler::reload(); } /** * Shutdowns the server correctly * @param bool $restart * @param string $msg */ public function shutdown(bool $restart = false, string $msg = ""){ /*if($this->isRunning){ $killer = new ServerKiller(90); $killer->start(); $killer->kill(); }*/ $this->isRunning = false; if($msg != ""){ $this->propertyCache["settings.shutdown-message"] = $msg; } } public function forceShutdown(){ if($this->hasStopped){ return; } try{ if(!$this->isRunning()){ $this->sendUsage(SendUsageTask::TYPE_CLOSE); } $this->hasStopped = true; $this->shutdown(); if($this->rcon instanceof RCON){ $this->rcon->stop(); } if($this->getProperty("network.upnp-forwarding", false) === true){ $this->logger->info("[UPnP] Removing port forward..."); UPnP::RemovePortForward($this->getPort()); } $this->getLogger()->debug("Disabling all plugins"); $this->pluginManager->disablePlugins(); foreach($this->players as $player){ $player->close($player->getLeaveMessage(), $this->getProperty("settings.shutdown-message", "Server closed")); } $this->getLogger()->debug("Unloading all levels"); foreach($this->getLevels() as $level){ $this->unloadLevel($level, true); } $this->getLogger()->debug("Removing event handlers"); HandlerList::unregisterAll(); $this->getLogger()->debug("Stopping all tasks"); $this->scheduler->cancelAllTasks(); $this->scheduler->mainThreadHeartbeat(PHP_INT_MAX); $this->getLogger()->debug("Saving properties"); $this->properties->save(); $this->getLogger()->debug("Closing console"); $this->console->shutdown(); $this->console->notify(); $this->getLogger()->debug("Stopping network interfaces"); foreach($this->network->getInterfaces() as $interface){ $interface->shutdown(); $this->network->unregisterInterface($interface); } //$this->memoryManager->doObjectCleanup(); gc_collect_cycles(); }catch(\Throwable $e){ $this->logger->logException($e); $this->logger->emergency("Crashed while crashing, killing process"); @kill(getmypid()); } } public function getQueryInformation(){ return $this->queryRegenerateTask; } /** * Starts the PocketMine-MP server and starts processing ticks and packets */ public function start(){ if($this->getConfigBoolean("enable-query", true) === true){ $this->queryHandler = new QueryHandler(); } foreach($this->getIPBans()->getEntries() as $entry){ $this->network->blockAddress($entry->getName(), -1); } if($this->getProperty("settings.send-usage", true)){ $this->sendUsageTicker = 6000; $this->sendUsage(SendUsageTask::TYPE_OPEN); } if($this->getProperty("network.upnp-forwarding", false) == true){ $this->logger->info("[UPnP] Trying to port forward..."); UPnP::PortForward($this->getPort()); } $this->tickCounter = 0; if(function_exists("pcntl_signal")){ pcntl_signal(SIGTERM, [$this, "handleSignal"]); pcntl_signal(SIGINT, [$this, "handleSignal"]); pcntl_signal(SIGHUP, [$this, "handleSignal"]); $this->dispatchSignals = true; } $this->logger->info($this->getLanguage()->translateString("pocketmine.server.defaultGameMode", [self::getGamemodeString($this->getGamemode())])); $this->logger->info($this->getLanguage()->translateString("pocketmine.server.startFinished", [round(microtime(true) - \pocketmine\START_TIME, 3)])); if(!file_exists($this->getPluginPath() . DIRECTORY_SEPARATOR . "GenisysPro")) @mkdir($this->getPluginPath() . DIRECTORY_SEPARATOR . "GenisysPro"); $this->tickProcessor(); $this->forceShutdown(); gc_collect_cycles(); } public function handleSignal($signo){ if($signo === SIGTERM or $signo === SIGINT or $signo === SIGHUP){ $this->shutdown(); } } public function exceptionHandler(\Throwable $e, $trace = null){ if($e === null){ return; } global $lastError; if($trace === null){ $trace = $e->getTrace(); } $errstr = $e->getMessage(); $errfile = $e->getFile(); $errno = $e->getCode(); $errline = $e->getLine(); $type = ($errno === E_ERROR or $errno === E_USER_ERROR) ? \LogLevel::ERROR : (($errno === E_USER_WARNING or $errno === E_WARNING) ? \LogLevel::WARNING : \LogLevel::NOTICE); if(($pos = strpos($errstr, "\n")) !== false){ $errstr = substr($errstr, 0, $pos); } $errfile = cleanPath($errfile); if($this->logger instanceof MainLogger){ $this->logger->logException($e, $trace); } $lastError = [ "type" => $type, "message" => $errstr, "fullFile" => $e->getFile(), "file" => $errfile, "line" => $errline, "trace" => @getTrace(1, $trace) ]; global $lastExceptionError, $lastError; $lastExceptionError = $lastError; $this->crashDump(); } public function crashDump(){ if($this->isRunning === false){ return; } if($this->sendUsageTicker > 0){ $this->sendUsage(SendUsageTask::TYPE_CLOSE); } $this->hasStopped = false; ini_set("error_reporting", 0); ini_set("memory_limit", -1); //Fix error dump not dumped on memory problems $this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.create")); try{ $dump = new CrashDump($this); }catch(\Throwable $e){ $this->logger->critical($this->getLanguage()->translateString("pocketmine.crash.error", $e->getMessage())); return; } $this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.submit", [$dump->getPath()])); if($this->getProperty("auto-report.enabled", true) !== false){ $report = true; $plugin = $dump->getData()["plugin"]; if(is_string($plugin)){ $p = $this->pluginManager->getPlugin($plugin); if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){ $report = false; } }elseif(\Phar::running(true) == ""){ $report = false; } if($dump->getData()["error"]["type"] === "E_PARSE" or $dump->getData()["error"]["type"] === "E_COMPILE_ERROR"){ $report = false; } if($report){ $reply = Utils::postURL("http://" . $this->getProperty("auto-report.host", "crash.pocketmine.net") . "/submit/api", [ "report" => "yes", "name" => $this->getName() . " " . $this->getPocketMineVersion(), "email" => "crash@pocketmine.net", "reportPaste" => base64_encode($dump->getEncodedData()) ]); if(($data = json_decode($reply)) !== false and isset($data->crashId)){ $reportId = $data->crashId; $reportUrl = $data->crashUrl; $this->logger->emergency($this->getLanguage()->translateString("pocketmine.crash.archive", [$reportUrl, $reportId])); } } } //$this->checkMemory(); //$dump .= "Memory Usage Tracking: \r\n" . chunk_split(base64_encode(gzdeflate(implode(";", $this->memoryStats), 9))) . "\r\n"; $this->forceShutdown(); $this->isRunning = false; @kill(getmypid()); exit(1); } public function __debugInfo(){ return []; } private function tickProcessor(){ $this->nextTick = microtime(true); while($this->isRunning){ $this->tick(); $next = $this->nextTick - 0.0001; if($next > microtime(true)){ @time_sleep_until($next); } } } public function onPlayerLogin(Player $player){ if($this->sendUsageTicker > 0){ $this->uniquePlayers[$player->getRawUniqueId()] = $player->getRawUniqueId(); } $this->sendFullPlayerListData($player); $player->dataPacket($this->craftingManager->getCraftingDataPacket()); } public function addPlayer($identifier, Player $player){ $this->players[$identifier] = $player; $this->identifiers[spl_object_hash($player)] = $identifier; } public function addOnlinePlayer(Player $player){ $this->playerList[$player->getRawUniqueId()] = $player; $this->updatePlayerListData($player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkinId(), $player->getSkinData()); } public function removeOnlinePlayer(Player $player){ if(isset($this->playerList[$player->getRawUniqueId()])){ unset($this->playerList[$player->getRawUniqueId()]); $pk = new PlayerListPacket(); $pk->type = PlayerListPacket::TYPE_REMOVE; $pk->entries[] = [$player->getUniqueId()]; $this->broadcastPacket($this->playerList, $pk); } } public function updatePlayerListData(UUID $uuid, $entityId, $name, $skinId, $skinData, array $players = null){ $pk = new PlayerListPacket(); $pk->type = PlayerListPacket::TYPE_ADD; $pk->entries[] = [$uuid, $entityId, $name, $skinId, $skinData]; $this->broadcastPacket($players === null ? $this->playerList : $players, $pk); } public function removePlayerListData(UUID $uuid, array $players = null){ $pk = new PlayerListPacket(); $pk->type = PlayerListPacket::TYPE_REMOVE; $pk->entries[] = [$uuid]; $this->broadcastPacket($players === null ? $this->playerList : $players, $pk); } public function sendFullPlayerListData(Player $p){ $pk = new PlayerListPacket(); $pk->type = PlayerListPacket::TYPE_ADD; foreach($this->playerList as $player){ if($p === $player){ continue; //fixes duplicates } $pk->entries[] = [$player->getUniqueId(), $player->getId(), $player->getDisplayName(), $player->getSkinId(), $player->getSkinData()]; } $p->dataPacket($pk); } private function checkTickUpdates($currentTick, $tickTime){ foreach($this->players as $p){ if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){ /* $p->close("", "Login timeout"); */ }elseif($this->alwaysTickPlayers){ $p->onUpdate($currentTick); } } //Do level ticks foreach($this->getLevels() as $level){ if($level->getTickRate() > $this->baseTickRate and --$level->tickRateCounter > 0){ continue; } try{ $levelTime = microtime(true); $level->doTick($currentTick); $tickMs = (microtime(true) - $levelTime) * 1000; $level->tickRateTime = $tickMs; if($this->autoTickRate){ if($tickMs < 50 and $level->getTickRate() > $this->baseTickRate){ $level->setTickRate($r = $level->getTickRate() - 1); if($r > $this->baseTickRate){ $level->tickRateCounter = $level->getTickRate(); } $this->getLogger()->debug("Raising level \"" . $level->getName() . "\" tick rate to " . $level->getTickRate() . " ticks"); }elseif($tickMs >= 50){ if($level->getTickRate() === $this->baseTickRate){ $level->setTickRate(max($this->baseTickRate + 1, min($this->autoTickRateLimit, floor($tickMs / 50)))); $this->getLogger()->debug("Level \"" . $level->getName() . "\" took " . round($tickMs, 2) . "ms, setting tick rate to " . $level->getTickRate() . " ticks"); }elseif(($tickMs / $level->getTickRate()) >= 50 and $level->getTickRate() < $this->autoTickRateLimit){ $level->setTickRate($level->getTickRate() + 1); $this->getLogger()->debug("Level \"" . $level->getName() . "\" took " . round($tickMs, 2) . "ms, setting tick rate to " . $level->getTickRate() . " ticks"); } $level->tickRateCounter = $level->getTickRate(); } } }catch(\Throwable $e){ $this->logger->critical($this->getLanguage()->translateString("pocketmine.level.tickError", [$level->getName(), $e->getMessage()])); if(\pocketmine\DEBUG > 1 and $this->logger instanceof MainLogger){ $this->logger->logException($e); } } } } public function doAutoSave(){ if($this->getAutoSave()){ Timings::$worldSaveTimer->startTiming(); foreach($this->players as $index => $player){ if($player->isOnline()){ $player->save(true); }elseif(!$player->isConnected()){ $this->removePlayer($player); } } foreach($this->getLevels() as $level){ $level->save(false); } Timings::$worldSaveTimer->stopTiming(); } } public function sendUsage($type = SendUsageTask::TYPE_STATUS){ $this->scheduler->scheduleAsyncTask(new SendUsageTask($this, $type, $this->uniquePlayers)); $this->uniquePlayers = []; } /** * @return BaseLang */ public function getLanguage(){ return $this->baseLang; } /** * @return bool */ public function isLanguageForced(){ return $this->forceLanguage; } /** * @return Network */ public function getNetwork(){ return $this->network; } /** * @return MemoryManager */ public function getMemoryManager(){ return $this->memoryManager; } private function titleTick(){ if(!Terminal::hasFormattingCodes()){ return; } $d = Utils::getRealMemoryUsage(); $u = Utils::getMemoryUsage(true); $usage = round(($u[0] / 1024) / 1024, 2) . "/" . round(($d[0] / 1024) / 1024, 2) . "/" . round(($u[1] / 1024) / 1024, 2) . "/" . round(($u[2] / 1024) / 1024, 2) . " MB @ " . Utils::getThreadCount() . " threads"; echo "\x1b]0;" . $this->getName() . $this->getFormattedVersion("-") . " | Online " . count($this->players) . "/" . $this->getMaxPlayers() . " | Memory " . $usage . " | U " . round($this->network->getUpload() / 1024, 2) . " D " . round($this->network->getDownload() / 1024, 2) . " kB/s | TPS " . $this->getTicksPerSecondAverage() . " | Load " . $this->getTickUsageAverage() . "%\x07"; $this->network->resetStatistics(); } /** * @param string $address * @param int $port * @param string $payload * * TODO: move this to Network */ public function handlePacket($address, $port, $payload){ try{ if(strlen($payload) > 2 and substr($payload, 0, 2) === "\xfe\xfd" and $this->queryHandler instanceof QueryHandler){ $this->queryHandler->handle($address, $port, $payload); } }catch(\Throwable $e){ if(\pocketmine\DEBUG > 1){ if($this->logger instanceof MainLogger){ $this->logger->logException($e); } } $this->getNetwork()->blockAddress($address, 600); } //TODO: add raw packet events } /** * @param $variable * @param null $defaultValue * @param Config|null $cfg * @return bool|mixed|null */ public function getAdvancedProperty($variable, $defaultValue = null, Config $cfg = null){ $vars = explode(".", $variable); $base = array_shift($vars); if($cfg == null) $cfg = $this->advancedConfig; if($cfg->exists($base)){ $base = $cfg->get($base); }else{ return $defaultValue; } while(count($vars) > 0){ $baseKey = array_shift($vars); if(is_array($base) and isset($base[$baseKey])){ $base = $base[$baseKey]; }else{ return $defaultValue; } } return $base; } public function updateQuery(){ try{ $this->getPluginManager()->callEvent($this->queryRegenerateTask = new QueryRegenerateEvent($this, 5)); if($this->queryHandler !== null){ $this->queryHandler->regenerateInfo(); } }catch(\Throwable $e){ $this->logger->logException($e); } } /** * Tries to execute a server tick */ private function tick(){ $tickTime = microtime(true); if(($tickTime - $this->nextTick) < -0.025){ //Allow half a tick of diff return false; } Timings::$serverTickTimer->startTiming(); ++$this->tickCounter; $this->checkConsole(); Timings::$connectionTimer->startTiming(); $this->network->processInterfaces(); if($this->rcon !== null){ $this->rcon->check(); } Timings::$connectionTimer->stopTiming(); Timings::$schedulerTimer->startTiming(); $this->scheduler->mainThreadHeartbeat($this->tickCounter); Timings::$schedulerTimer->stopTiming(); $this->checkTickUpdates($this->tickCounter, $tickTime); foreach($this->players as $player){ $player->checkNetwork(); } if(($this->tickCounter & 0b1111) === 0){ $this->titleTick(); $this->maxTick = 20; $this->maxUse = 0; if(($this->tickCounter & 0b111111111) === 0){ if(($this->dserverConfig["enable"] and $this->dserverConfig["queryTickUpdate"]) or !$this->dserverConfig["enable"]){ $this->updateQuery(); } } if($this->dserverConfig["enable"] and $this->dserverConfig["motdPlayers"]){ $max = $this->getDServerMaxPlayers(); $online = $this->getDServerOnlinePlayers(); $name = $this->getNetwork()->getName().'['.$online.'/'.$max.']'; $this->getNetwork()->setName($name); //TODO: 检测是否爆满,不同状态颜色 } $this->getNetwork()->updateName(); } if($this->autoSave and ++$this->autoSaveTicker >= $this->autoSaveTicks){ $this->autoSaveTicker = 0; $this->doAutoSave(); } /*if($this->sendUsageTicker > 0 and --$this->sendUsageTicker === 0){ $this->sendUsageTicker = 6000; $this->sendUsage(SendUsageTask::TYPE_STATUS); }*/ if(($this->tickCounter % 100) === 0){ foreach($this->levels as $level){ $level->clearCache(); } if($this->getTicksPerSecondAverage() < 1){ $this->logger->warning($this->getLanguage()->translateString("pocketmine.server.tickOverload")); } } if($this->dispatchSignals and $this->tickCounter % 5 === 0){ pcntl_signal_dispatch(); } $this->getMemoryManager()->check(); Timings::$serverTickTimer->stopTiming(); $now = microtime(true); $tick = min(20, 1 / max(0.001, $now - $tickTime)); $use = min(1, ($now - $tickTime) / 0.05); //TimingsHandler::tick($tick <= $this->profilingTickRate); if($this->maxTick > $tick){ $this->maxTick = $tick; } if($this->maxUse < $use){ $this->maxUse = $use; } array_shift($this->tickAverage); $this->tickAverage[] = $tick; array_shift($this->useAverage); $this->useAverage[] = $use; if(($this->nextTick - $tickTime) < -1){ $this->nextTick = $tickTime; }else{ $this->nextTick += 0.05; } return true; } } classLoader; } /** * @param \ClassLoader|null $loader */ public function setClassLoader(\ClassLoader $loader = null){ if($loader === null){ $loader = Server::getInstance()->getLoader(); } $this->classLoader = $loader; } public function registerClassLoader(){ if(!interface_exists("ClassLoader", false)){ require(\pocketmine\PATH . "src/spl/ClassLoader.php"); require(\pocketmine\PATH . "src/spl/BaseClassLoader.php"); } if($this->classLoader !== null){ $this->classLoader->register(true); } } /** * @param int $options * * @return bool */ public function start(int $options = PTHREADS_INHERIT_ALL){ ThreadManager::getInstance()->add($this); if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){ if($this->getClassLoader() === null){ $this->setClassLoader(); } return parent::start($options); } return false; } /** * Stops the thread using the best way possible. Try to stop it yourself before calling this. */ public function quit(){ $this->isKilled = true; $this->notify(); if(!$this->isJoined()){ if(!$this->isTerminated()){ $this->join(); } } ThreadManager::getInstance()->remove($this); } /** * @return string */ public function getThreadName(){ return (new \ReflectionClass($this))->getShortName(); } } {spl_object_hash($thread)} = $thread; } } /** * @param Worker|Thread $thread */ public function remove($thread){ if($thread instanceof Thread or $thread instanceof Worker){ unset($this->{spl_object_hash($thread)}); } } /** * @return Worker[]|Thread[] */ public function getAll(){ $array = []; foreach($this as $key => $thread){ $array[$key] = $thread; } return $array; } }classLoader; } /** * @param \ClassLoader|null $loader */ public function setClassLoader(\ClassLoader $loader = null){ if($loader === null){ $loader = Server::getInstance()->getLoader(); } $this->classLoader = $loader; } public function registerClassLoader(){ if(!interface_exists("ClassLoader", false)){ require(\pocketmine\PATH . "src/spl/ClassLoader.php"); require(\pocketmine\PATH . "src/spl/BaseClassLoader.php"); } if($this->classLoader !== null){ $this->classLoader->register(true); } } /** * @param int $options * * @return bool */ public function start(int $options = PTHREADS_INHERIT_ALL){ ThreadManager::getInstance()->add($this); if(!$this->isRunning() and !$this->isJoined() and !$this->isTerminated()){ if($this->getClassLoader() === null){ $this->setClassLoader(); } return parent::start($options); } return false; } /** * Stops the thread using the best way possible. Try to stop it yourself before calling this. */ public function quit(){ $this->isKilled = true; $this->notify(); if($this->isRunning()){ $this->shutdown(); $this->notify(); $this->unstack(); }elseif(!$this->isJoined()){ if(!$this->isTerminated()){ $this->join(); } } ThreadManager::getInstance()->remove($this); } /** * @return string */ public function getThreadName(){ return (new \ReflectionClass($this))->getShortName(); } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Acacia Door Block"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::ACACIA_DOOR, 0, 1], ]; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Activator Rail"; } } meta = $meta; } public function getName() : string{ return "Active Redstone Lamp"; } public function getHardness() { return 0.3; } public function getToolType(){ return Tool::TYPE_PICKAXE; } public function getLightLevel(){ return 15; } public function getDrops(Item $item) : array { return [ [Item::INACTIVE_REDSTONE_LAMP, 0 ,1], ]; } public function isLightedByAround(){ return ($this->meta == 1); } protected function checkPower(array $ignore = []){ if($this->isLightedByAround()){ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP, Vector3::SIDE_DOWN]; foreach($sides as $side){ if(!in_array($side, $ignore)){ /** @var ActiveRedstoneLamp $block */ $block = $this->getSide($side); if($block->getId() == $this->id){ if(!$block->isLightedByAround()) return true; } } } } return false; } public function lightAround(){ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP, Vector3::SIDE_DOWN]; foreach($sides as $side){ /** @var InactiveRedstoneLamp $block */ $block = $this->getSide($side); if($block->getId() == self::INACTIVE_REDSTONE_LAMP){ $block->turnOn(); } } } protected function turnAroundOff(array $ignore = []){ if(!$this->isLightedByAround()){ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP, Vector3::SIDE_DOWN]; foreach($sides as $side){ if(!in_array($side, $ignore)){ /** @var ActiveRedstoneLamp $block */ $block = $this->getSide($side); if($block->getId() == $this->id){ if($block->isLightedByAround()){ if(!$block->checkPower([$this->getOppositeSide($side)])) $block->turnOff(); } } } } } } public function turnOn(){ /*if($this->isLightedByAround()){ $this->meta = 0; $this->getLevel()->setBlock($this, $this, true, false); $this->lightAround(); }*/ $this->meta = 0; $this->getLevel()->setBlock($this, $this, true, false); return true; } public function turnOff(){ $this->getLevel()->setBlock($this, new InactiveRedstoneLamp(), true, true); //$this->turnAroundOff(); return true; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Air"; } /** * @return bool */ public function canPassThrough(){ return true; } /** * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return false; } /** * @return bool */ public function canBeFlowedInto(){ return true; } /** * @return bool */ public function canBeReplaced(){ return true; } /** * @return bool */ public function canBePlaced(){ return false; } /** * @return bool */ public function isSolid(){ return false; } /** * @return null */ public function getBoundingBox(){ return null; } /** * @return int */ public function getHardness(){ return 0; } /** * @return int */ public function getResistance(){ return 0; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 5; } /** * @return int */ public function getResistance(){ return 6000; } /** * @return string */ public function getName() : string{ $names = [ self::NORMAL => "Anvil", self::SLIGHTLY_DAMAGED => "Slightly Damaged Anvil", self::VERY_DAMAGED => "Very Damaged Anvil", 12 => "Anvil" //just in case somebody uses /give to get an anvil with damage 12 or higher, to prevent crash ]; return $names[$this->meta & 0x0c]; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if(!$this->getLevel()->getServer()->anvilEnabled){ return true; } if($player instanceof Player){ if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow(new AnvilInventory($this)); $player->craftingType = Player::CRAFTING_ANVIL; } return true; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool|void */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $direction = ($player !== null ? $player->getDirection() : 0) & 0x03; $this->meta = ($this->meta & 0x0c) | $direction; $this->getLevel()->setBlock($block, $this, true, true); $player->getLevel()->broadcastLevelEvent($player, LevelEventPacket::EVENT_SOUND_ANVIL_FALL); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [$this->id, $this->meta & 0x0c, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return string */ public function getName(){ return "Beacon"; } /** * @return int */ public function getLightLevel(){ return 15; } /** * @return int */ public function getResistance(){ return 15; } /** * @return int */ public function getHardness(){ return 3; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->getLevel()->setBlock($this, $this, true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::BEACON), new ByteTag("isMovable", (bool) false), new IntTag("primary", 0), new IntTag("secondary", 0), new IntTag("x", $block->x), new IntTag("y", $block->y), new IntTag("z", $block->z) ]); Tile::createTile(Tile::BEACON, $this->getLevel(), $nbt); return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $top = $this->getSide(1); if($top->isTransparent() !== true){ return true; } $t = $this->getLevel()->getTile($this); $beacon = null; if($t instanceof TileBeacon){ $beacon = $t; }else{ $nbt = new CompoundTag("", [ new StringTag("id", Tile::BEACON), new ByteTag("isMovable", (bool) false), new IntTag("primary", 0), new IntTag("secondary", 0), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); Tile::createTile(Tile::BEACON, $this->getLevel(), $nbt); } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow($beacon->getInventory()); } return true; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ $this->getLevel()->setBlock($this, new Air(), true, true); return true; } } meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 0.2; } /** * @return string */ public function getName() : string{ return "Bed Block"; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.5625, $this->z + 1 ); } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ $dimension = $this->getLevel()->getDimension(); if($dimension == Level::DIMENSION_NETHER or $dimension == Level::DIMENSION_END){ $explosion = new Explosion($this, 6, $this); $explosion->explodeA(); return true; } $time = $this->getLevel()->getTime() % Level::TIME_FULL; $isNight = ($time >= Level::TIME_NIGHT and $time < Level::TIME_SUNRISE); if($player instanceof Player and !$isNight){ $player->sendMessage(TextFormat::GRAY . "You can only sleep at night"); //TODO; Translate it return true; } $blockNorth = $this->getSide(2); //Gets the blocks around them $blockSouth = $this->getSide(3); $blockEast = $this->getSide(5); $blockWest = $this->getSide(4); if(($this->meta & 0x08) === 0x08){ //This is the Top part of bed $b = $this; }else{ //Bottom Part of Bed if($blockNorth->getId() === $this->id and ($blockNorth->meta & 0x08) === 0x08){ $b = $blockNorth; }elseif($blockSouth->getId() === $this->id and ($blockSouth->meta & 0x08) === 0x08){ $b = $blockSouth; }elseif($blockEast->getId() === $this->id and ($blockEast->meta & 0x08) === 0x08){ $b = $blockEast; }elseif($blockWest->getId() === $this->id and ($blockWest->meta & 0x08) === 0x08){ $b = $blockWest; }else{ if($player instanceof Player){ $player->sendMessage(TextFormat::GRAY . "This bed is incomplete"); //TODO; Translate it } return true; } } if($player instanceof Player and $player->sleepOn($b) === false){ $player->sendMessage(TextFormat::GRAY . "This bed is occupied"); //TODO; Translate it } return true; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->isTransparent() === false){ $faces = [ 0 => 3, 1 => 4, 2 => 2, 3 => 5, ]; $d = $player instanceof Player ? $player->getDirection() : 0; $next = $this->getSide($faces[(($d + 3) % 4)]); $downNext = $this->getSide(0); if($next->canBeReplaced() === true and $downNext->isTransparent() === false){ $meta = (($d + 3) % 4) & 0x03; $this->getLevel()->setBlock($block, Block::get($this->id, $meta), true, true); $this->getLevel()->setBlock($next, Block::get($this->id, $meta | 0x08), true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::BED), new ByteTag("color", $item->getDamage() & 0x0f), new IntTag("x", $block->x), new IntTag("y", $block->y), new IntTag("z", $block->z), ]); $nbt2 = clone $nbt; $nbt2["x"] = $next->x; $nbt2["z"] = $next->z; Tile::createTile(Tile::BED, $this->getLevel(), $nbt); Tile::createTile(Tile::BED, $this->getLevel(), $nbt2); return true; } } return false; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ $sides = [ 0 => 3, 1 => 4, 2 => 2, 3 => 5, 8 => 2, 9 => 5, 10 => 3, 11 => 4, ]; if(($this->meta & 0x08) === 0x08){ //This is the Top part of bed $next = $this->getSide($sides[$this->meta]); if($next->getId() === $this->id and ($next->meta | 0x08) === $this->meta){ //Checks if the block ID and meta are right $this->getLevel()->setBlock($next, new Air(), true, true); } }else{ //Bottom Part of Bed $next = $this->getSide($sides[$this->meta]); if($next->getId() === $this->id and $next->meta === ($this->meta | 0x08)){ $this->getLevel()->setBlock($next, new Air(), true, true); } } $this->getLevel()->setBlock($this, new Air(), true, true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $tile = $this->getLevel()->getTile($this); if($tile instanceof TileBed){ return [ [Item::BED, $tile->getColor(), 1] ]; }else{ return [ [Item::BED, 14, 1] //Red ]; } } /** * @return int */ public function getVariantBitmask(){ return 0x08; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Bedrock"; } /** * @return int */ public function getHardness(){ return -1; } /** * @return int */ public function getResistance(){ return 18000000; } /** * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return false; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Beetroot Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 0x07){ $drops[] = [Item::BEETROOT, 0, 1]; $drops[] = [Item::BEETROOT_SEEDS, 0, mt_rand(0, 3)]; }else{ $drops[] = [Item::BEETROOT_SEEDS, 0, 1]; } return $drops; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Birch Door Block"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::BIRCH_DOOR, 0, 1], ]; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Black Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } } $class){ if($class !== null){ /** @var Block $block */ $block = new $class(); for($data = 0; $data < 16; ++$data){ self::$fullList[($id << 4) | $data] = new $class($data); } self::$solid[$id] = $block->isSolid(); self::$transparent[$id] = $block->isTransparent(); self::$hardness[$id] = $block->getHardness(); self::$light[$id] = $block->getLightLevel(); if($block->isSolid()){ if($block->isTransparent()){ if($block instanceof Liquid or $block instanceof Ice){ self::$lightFilter[$id] = 2; }else{ self::$lightFilter[$id] = 1; } }elseif($block instanceof SolidLight){ self::$lightFilter[$id] = 1; }else{ self::$lightFilter[$id] = 15; } }else{ self::$lightFilter[$id] = 1; } }else{ self::$lightFilter[$id] = 1; for($data = 0; $data < 16; ++$data){ self::$fullList[($id << 4) | $data] = new Block($id, $data); } } } } } /** * @param int $id * @param int $meta * @param Position $pos * * @return Block */ public static function get($id, $meta = 0, Position $pos = null){ if($id > 0xff){ trigger_error("BlockID cannot be higher than 255, defaulting to 0", E_USER_NOTICE); $id = 0; } try{ $block = self::$list[$id]; if($block !== null){ $block = new $block($meta); }else{ $block = new Block($id, $meta); } }catch(\RuntimeException $e){ $block = new Block($id, $meta); } if($pos !== null){ $block->x = $pos->x; $block->y = $pos->y; $block->z = $pos->z; $block->level = $pos->level; } return $block; } /** * @param int $id * @param int $meta */ public function __construct($id, $meta = 0){ $this->id = (int) $id; $this->meta = (int) $meta; } /** * Places the Block, using block space and block target, and side. Returns if the block has been placed. * * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player $player = null * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ return $this->getLevel()->setBlock($this, $this, true, true); } /** * Returns if the item can be broken with an specific Item * * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return true; } public function tickRate() : int{ return 10; } /** * Do the actions needed so the block is broken with the Item * * @param Item $item * * @return mixed */ public function onBreak(Item $item){ return $this->getLevel()->setBlock($this, new Air(), true, true); } /** * Fires a block update on the Block * * @param int $type * * @return void */ public function onUpdate($type){ } /** * Do actions when activated by Item. Returns if it has done anything * * @param Item $item * @param Player $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ return false; } /** * @return int */ public function getHardness(){ return 10; } /** * @return int */ public function getResistance(){ return $this->getHardness() * 5; } /** * @return int */ public function getBurnChance() : int{ return 0; } /** * @return int */ public function getBurnAbility() : int{ return 0; } public function isTopFacingSurfaceSolid(){ if($this->isSolid()){ return true; }else{ if($this instanceof Stair and ($this->getDamage() &4) == 4){ return true; }elseif($this instanceof Slab and ($this->getDamage() & 8) == 8){ return true; }elseif($this instanceof SnowLayer and ($this->getDamage() & 7) == 7){ return true; } } return false; } public function canNeighborBurn(){ for($face = 0; $face < 5; $face++){ if($this->getSide($face)->getBurnChance() > 0){ return true; } } return false; } /** * @return int */ public function getToolType(){ return Tool::TYPE_NONE; } /** * @return float */ public function getFrictionFactor(){ return 0.6; } /** * @return int 0-15 */ public function getLightLevel(){ return 0; } /** * AKA: Block->isPlaceable * * @return bool */ public function canBePlaced(){ return true; } public function isPlaceable(){ return $this->canBePlaced(); } /** * AKA: Block->canBeReplaced() * * @return bool */ public function canBeReplaced(){ return false; } /** * @return bool */ public function isTransparent(){ return false; } public function isSolid(){ return true; } /** * AKA: Block->isFlowable * * @return bool */ public function canBeFlowedInto(){ return false; } /** * AKA: Block->isActivable * * @return bool */ public function canBeActivated() : bool{ return false; } public function activate(){ return false; } public function deactivate(){ return false; } public function isActivated(Block $from = null){ return false; } public function hasEntityCollision(){ return false; } public function canPassThrough(){ return false; } /** * @return string */ public function getName(){ return "Unknown"; } /** * @return int */ final public function getId(){ return $this->id; } public function addVelocityToEntity(Entity $entity, Vector3 $vector){ } /** * @return int */ final public function getDamage(){ return $this->meta; } /** * @param int $meta */ final public function setDamage($meta){ $this->meta = $meta & 0x0f; } /** * Sets the block position to a new Position object * * @param Position $v */ final public function position(Position $v){ $this->x = (int) $v->x; $this->y = (int) $v->y; $this->z = (int) $v->z; $this->level = $v->level; $this->boundingBox = null; } /** * Returns an array of Item objects to be dropped * * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if(!isset(self::$list[$this->getId()])){ //Unknown blocks return []; }else{ return [ [$this->getId(), $this->getDamage(), 1], ]; } } /** * Returns the seconds that this block takes to be broken using an specific Item * * @param Item $item * * @return float */ public function getBreakTime(Item $item){ $base = $this->getHardness() * 1.5; if($this->canBeBrokenWith($item)){ if($this->getToolType() === Tool::TYPE_SHEARS and $item->isShears()){ $base /= 15; }elseif( ($this->getToolType() === Tool::TYPE_PICKAXE and ($tier = $item->isPickaxe()) !== false) or ($this->getToolType() === Tool::TYPE_AXE and ($tier = $item->isAxe()) !== false) or ($this->getToolType() === Tool::TYPE_SHOVEL and ($tier = $item->isShovel()) !== false) ){ switch($tier){ case Tool::TIER_WOODEN: $base /= 2; break; case Tool::TIER_STONE: $base /= 4; break; case Tool::TIER_IRON: $base /= 6; break; case Tool::TIER_DIAMOND: $base /= 8; break; case Tool::TIER_GOLD: $base /= 12; break; } } }else{ $base *= 3.33; } if($item->isSword()){ $base *= 0.5; } return $base; } public function canBeBrokenWith(Item $item){ return $this->getHardness() !== -1; } /** * Returns the Block on the side $side, works like Vector3::side() * * @param int $side * @param int $step * * @return Block */ public function getSide($side, $step = 1){ if($this->isValid()){ return $this->getLevel()->getBlock(Vector3::getSide($side, $step)); } return Block::get(Item::AIR, 0, Position::fromObject(Vector3::getSide($side, $step))); } /** * @return string */ public function __toString(){ return "Block[" . $this->getName() . "] (" . $this->getId() . ":" . $this->getDamage() . ")"; } /** * Checks for collision against an AxisAlignedBB * * @param AxisAlignedBB $bb * * @return bool */ public function collidesWithBB(AxisAlignedBB $bb){ $bb2 = $this->getBoundingBox(); return $bb2 !== null and $bb->intersectsWith($bb2); } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ } /** * @return AxisAlignedBB */ public function getBoundingBox(){ if($this->boundingBox === null){ $this->boundingBox = $this->recalculateBoundingBox(); } return $this->boundingBox; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); } /** * @param Vector3 $pos1 * @param Vector3 $pos2 * * @return MovingObjectPosition */ public function calculateIntercept(Vector3 $pos1, Vector3 $pos2){ $bb = $this->getBoundingBox(); if($bb === null){ return null; } $v1 = $pos1->getIntermediateWithXValue($pos2, $bb->minX); $v2 = $pos1->getIntermediateWithXValue($pos2, $bb->maxX); $v3 = $pos1->getIntermediateWithYValue($pos2, $bb->minY); $v4 = $pos1->getIntermediateWithYValue($pos2, $bb->maxY); $v5 = $pos1->getIntermediateWithZValue($pos2, $bb->minZ); $v6 = $pos1->getIntermediateWithZValue($pos2, $bb->maxZ); if($v1 !== null and !$bb->isVectorInYZ($v1)){ $v1 = null; } if($v2 !== null and !$bb->isVectorInYZ($v2)){ $v2 = null; } if($v3 !== null and !$bb->isVectorInXZ($v3)){ $v3 = null; } if($v4 !== null and !$bb->isVectorInXZ($v4)){ $v4 = null; } if($v5 !== null and !$bb->isVectorInXY($v5)){ $v5 = null; } if($v6 !== null and !$bb->isVectorInXY($v6)){ $v6 = null; } $vector = $v1; if($v2 !== null and ($vector === null or $pos1->distanceSquared($v2) < $pos1->distanceSquared($vector))){ $vector = $v2; } if($v3 !== null and ($vector === null or $pos1->distanceSquared($v3) < $pos1->distanceSquared($vector))){ $vector = $v3; } if($v4 !== null and ($vector === null or $pos1->distanceSquared($v4) < $pos1->distanceSquared($vector))){ $vector = $v4; } if($v5 !== null and ($vector === null or $pos1->distanceSquared($v5) < $pos1->distanceSquared($vector))){ $vector = $v5; } if($v6 !== null and ($vector === null or $pos1->distanceSquared($v6) < $pos1->distanceSquared($vector))){ $vector = $v6; } if($vector === null){ return null; } $f = -1; if($vector === $v1){ $f = 4; }elseif($vector === $v2){ $f = 5; }elseif($vector === $v3){ $f = 0; }elseif($vector === $v4){ $f = 1; }elseif($vector === $v5){ $f = 2; }elseif($vector === $v6){ $f = 3; } return MovingObjectPosition::fromBlock($this->x, $this->y, $this->z, $f, $vector->add($this->x, $this->y, $this->z)); } public function setMetadata($metadataKey, MetadataValue $metadataValue){ if($this->getLevel() instanceof Level){ $this->getLevel()->getBlockMetadata()->setMetadata($this, $metadataKey, $metadataValue); } } public function getMetadata($metadataKey){ if($this->getLevel() instanceof Level){ return $this->getLevel()->getBlockMetadata()->getMetadata($this, $metadataKey); } return null; } public function hasMetadata($metadataKey){ if($this->getLevel() instanceof Level){ $this->getLevel()->getBlockMetadata()->hasMetadata($this, $metadataKey); } } public function removeMetadata($metadataKey, Plugin $plugin){ if($this->getLevel() instanceof Level){ $this->getLevel()->getBlockMetadata()->removeMetadata($this, $metadataKey, $plugin); } } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Blue Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Bookshelf"; } /** * @return float */ public function getHardness(){ return 1.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return int */ public function getBurnChance() : int{ return 30; } /** * @return int */ public function getBurnAbility() : int{ return 20; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::BOOK, 0, 3] ]; } }meta = $meta; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($block->getSide(Vector3::SIDE_DOWN)->isTransparent() === false){ $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::BREWING_STAND), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::BREWING_STAND, $this->getLevel(), $nbt); return true; } return false; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return float */ public function getResistance(){ return 2.5; } /** * @return int */ public function getLightLevel(){ return 1; } /** * @return string */ public function getName() : string{ return "Brewing Stand"; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ //TODO lock if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $t = $this->getLevel()->getTile($this); //$brewingStand = false; if($t instanceof TileBrewingStand){ $brewingStand = $t; }else{ $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::BREWING_STAND), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); $brewingStand = Tile::createTile(Tile::BREWING_STAND, $this->getLevel(), $nbt); } $player->addWindow($brewingStand->getInventory()); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($item->isPickaxe() >= Tool::TIER_WOODEN){ $drops[] = [Item::BREWING_STAND, 0, 1]; } return $drops; } } meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getResistance(){ return 30; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Brick Stairs"; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getResistance(){ return 30; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Bricks"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::BRICKS_BLOCK, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Brown Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Brown Mushroom"; } /** * @return int */ public function getLightLevel(){ return 1; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->isTransparent() === false){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @return null */ public function getBoundingBox(){ return null; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return string */ public function getName() : string{ return "Brown Mushroom Block"; } /** * @return float */ public function getHardness(){ return 0.2; } /** * @return int */ public function getResistance(){ return 1; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::BROWN_MUSHROOM_BLOCK, self::BROWN, 1], ]; }else{ return [ [Item::BROWN_MUSHROOM, 0, mt_rand(0, 2)], ]; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Burning Furnace"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 3.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getLightLevel(){ return 13; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::FURNACE), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile("Furnace", $this->getLevel(), $nbt); return true; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ $this->getLevel()->setBlock($this, new Air(), true, true); return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $furnace = $this->getLevel()->getTile($this); if(!($furnace instanceof TileFurnace)){ $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::FURNACE), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); $furnace = Tile::createTile("Furnace", $this->getLevel(), $nbt); } if(isset($furnace->namedtag->Lock) and $furnace->namedtag->Lock instanceof StringTag){ if($furnace->namedtag->Lock->getValue() !== $item->getCustomName()){ return true; } } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow($furnace->getInventory()); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($item->isPickaxe() >= Tool::TIER_WOODEN){ $drops[] = [Item::FURNACE, 0, 1]; } return $drops; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.4; } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @return string */ public function getName() : string{ return "Cactus"; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x + 0.0625, $this->y + 0.0625, $this->z + 0.0625, $this->x + 0.9375, $this->y + 0.9375, $this->z + 0.9375 ); } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_CONTACT, 1); if($entity->attack($ev->getFinalDamage(), $ev) === true){ $ev->useArmors(); } } /** * @param int $type * * @return bool */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $down = $this->getSide(0); if($down->getId() !== self::SAND and $down->getId() !== self::CACTUS){ $this->getLevel()->useBreakOn($this); }else{ for($side = 2; $side <= 5; ++$side){ $b = $this->getSide($side); if(!$b->canBeFlowedInto()){ $this->getLevel()->useBreakOn($this); } } } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if($this->getSide(0)->getId() !== self::CACTUS){ if($this->meta == 0x0F){ for($y = 1; $y < 3; ++$y){ $b = $this->getLevel()->getBlock(new Vector3($this->x, $this->y + $y, $this->z)); if($b->getId() === self::AIR){ Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($b, new Cactus())); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($b, $ev->getNewState(), true); } } } $this->meta = 0; $this->getLevel()->setBlock($this, $this); }else{ ++$this->meta; $this->getLevel()->setBlock($this, $this); } } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === self::SAND or $down->getId() === self::CACTUS){ $block0 = $this->getSide(2); $block1 = $this->getSide(3); $block2 = $this->getSide(4); $block3 = $this->getSide(5); if($block0->isTransparent() === true and $block1->isTransparent() === true and $block2->isTransparent() === true and $block3->isTransparent() === true){ $this->getLevel()->setBlock($this, $this, true); return true; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return string */ public function getName() : string{ return "Cake Block"; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ $f = (1 + $this->getDamage() * 2) / 16; return new AxisAlignedBB( $this->x + $f, $this->y, $this->z + 0.0625, $this->x + 1 - 0.0625, $this->y + 0.5, $this->z + 1 - 0.0625 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() !== self::AIR){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->getId() === self::AIR){ //Replace with common break method $this->getLevel()->setBlock($this, new Air(), true); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return []; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player and $player->getFood() < $player->getMaxFood()){ $ev = new EntityEatBlockEvent($player, $this); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getResidue()); return true; } } return false; } /** * @return int */ public function getFoodRestore() : int{ return 2; } /** * @return float */ public function getSaturationRestore() : float{ return 0.4; } /** * @return Air|Cake */ public function getResidue(){ $clone = clone $this; $clone->meta++; if($clone->meta >= 0x06){ $clone = new Air(); } return $clone; } /** * @return Effect[] */ public function getAdditionalEffects() : array{ return []; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 0.1; } /** * @return bool */ public function isSolid(){ return true; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "White Carpet", 1 => "Orange Carpet", 2 => "Magenta Carpet", 3 => "Light Blue Carpet", 4 => "Yellow Carpet", 5 => "Lime Carpet", 6 => "Pink Carpet", 7 => "Gray Carpet", 8 => "Light Gray Carpet", 9 => "Cyan Carpet", 10 => "Purple Carpet", 11 => "Blue Carpet", 12 => "Brown Carpet", 13 => "Green Carpet", 14 => "Red Carpet", 15 => "Black Carpet", ]; return $names[$this->meta & 0x0f]; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.0625, $this->z + 1 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() !== self::AIR){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->getId() === self::AIR){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Carrot Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 0x07){ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $drops[] = [Item::CARROT, 0, mt_rand(1, 4 + $fortunel)]; }else{ $drops[] = [Item::CARROT, 0, 1]; } return $drops; } } meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return string */ public function getName() : string{ return "Cauldron"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $nbt = new CompoundTag("", [ new StringTag("id", Tile::CAULDRON), new IntTag("x", $block->x), new IntTag("y", $block->y), new IntTag("z", $block->z), new ShortTag("PotionId", 0xffff), new ByteTag("SplashPotion", 0), new ListTag("Items", []) ]); if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile("Cauldron", $this->getLevel(), $nbt); $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ $this->getLevel()->setBlock($this, new Air(), true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::CAULDRON, 0, 1] ]; } return []; } public function update(){//umm... right update method...? $this->getLevel()->setBlock($this, Block::get($this->id, $this->meta + 1), true); $this->getLevel()->setBlock($this, $this, true);//Undo the damage value } /** * @return bool */ public function isEmpty(){ return $this->meta === 0x00; } /** * @return bool */ public function isFull(){ return $this->meta === 0x06; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){//@author iTX. rewrite @Dog194 $tile = $this->getLevel()->getTile($this); if(!($tile instanceof TileCauldron)){ return false; } switch($item->getId()){ case Item::BUCKET: if($item->getDamage() === 0){//empty bucket if(!$this->isFull() or $tile->isCustomColor() or $tile->hasPotion()){ break; } $bucket = clone $item; $bucket->setDamage(8);//water bucket Server::getInstance()->getPluginManager()->callEvent($ev = new PlayerBucketFillEvent($player, $this, 0, $item, $bucket)); if(!$ev->isCancelled()){ if($player->isSurvival()){ $player->getInventory()->setItemInHand($ev->getItem()); } $this->meta = 0;//empty $this->getLevel()->setBlock($this, $this, true); $tile->clearCustomColor(); $this->getLevel()->addSound(new SplashSound($this->add(0.5, 1, 0.5))); } }elseif($item->getDamage() === 8){//water bucket if($this->isFull() and !$tile->isCustomColor() and !$tile->hasPotion()){ break; } $bucket = clone $item; $bucket->setDamage(0);//empty bucket Server::getInstance()->getPluginManager()->callEvent($ev = new PlayerBucketEmptyEvent($player, $this, 0, $item, $bucket)); if(!$ev->isCancelled()){ if($player->isSurvival()){ $player->getInventory()->setItemInHand($ev->getItem()); } if($tile->hasPotion()){//if has potion $this->meta = 0;//empty $tile->setPotionId(0xffff);//reset potion $tile->setSplashPotion(false); $tile->clearCustomColor(); $this->getLevel()->setBlock($this, $this, true); $this->getLevel()->addSound(new ExplodeSound($this->add(0.5, 0, 0.5))); }else{ $this->meta = 6;//fill $tile->clearCustomColor(); $this->getLevel()->setBlock($this, $this, true); $this->getLevel()->addSound(new SplashSound($this->add(0.5, 1, 0.5))); } $this->update(); } } break; case Item::DYE: if($tile->hasPotion()) break; $color = Color::getDyeColor($item->getDamage()); if($tile->isCustomColor()){ $color = Color::averageColor($color, $tile->getCustomColor()); } if($player->isSurvival()){ $item->setCount($item->getCount() - 1); /*if($item->getCount() <= 0){ $player->getInventory()->setItemInHand(Item::get(Item::AIR)); }*/ } $tile->setCustomColor($color); $this->getLevel()->addSound(new SplashSound($this->add(0.5, 1, 0.5))); $this->update(); break; case Item::LEATHER_CAP: case Item::LEATHER_TUNIC: case Item::LEATHER_PANTS: case Item::LEATHER_BOOTS: if($this->isEmpty()) break; if($tile->isCustomColor()){ --$this->meta; $this->getLevel()->setBlock($this, $this, true); $newItem = clone $item; /** @var Armor $newItem */ $newItem->setCustomColor($tile->getCustomColor()); $player->getInventory()->setItemInHand($newItem); $this->getLevel()->addSound(new SplashSound($this->add(0.5, 1, 0.5))); if($this->isEmpty()){ $tile->clearCustomColor(); } }else{ --$this->meta; $this->getLevel()->setBlock($this, $this, true); $newItem = clone $item; /** @var Armor $newItem */ $newItem->clearCustomColor(); $player->getInventory()->setItemInHand($newItem); $this->getLevel()->addSound(new SplashSound($this->add(0.5, 1, 0.5))); } break; case Item::POTION: case Item::SPLASH_POTION: if(!$this->isEmpty() and (($tile->getPotionId() !== $item->getDamage() and $item->getDamage() !== Potion::WATER_BOTTLE) or ($item->getId() === Item::POTION and $tile->getSplashPotion()) or ($item->getId() === Item::SPLASH_POTION and !$tile->getSplashPotion()) and $item->getDamage() !== 0 or ($item->getDamage() === Potion::WATER_BOTTLE and $tile->hasPotion())) ){//long... $this->meta = 0x00; $this->getLevel()->setBlock($this, $this, true); $tile->setPotionId(0xffff);//reset $tile->setSplashPotion(false); $tile->clearCustomColor(); if($player->isSurvival()){ $player->getInventory()->setItemInHand(Item::get(Item::GLASS_BOTTLE)); } $this->getLevel()->addSound(new ExplodeSound($this->add(0.5, 0, 0.5))); }elseif($item->getDamage() === Potion::WATER_BOTTLE){//水瓶 喷溅型水瓶 $this->meta += 2; if($this->meta > 0x06) $this->meta = 0x06; $this->getLevel()->setBlock($this, $this, true); if($player->isSurvival()){ $player->getInventory()->setItemInHand(Item::get(Item::GLASS_BOTTLE)); } $tile->setPotionId(0xffff); $tile->setSplashPotion(false); $tile->clearCustomColor(); $this->getLevel()->addSound(new SplashSound($this->add(0.5, 1, 0.5))); }elseif(!$this->isFull()){ $this->meta += 2; if($this->meta > 0x06) $this->meta = 0x06; $tile->setPotionId($item->getDamage()); $tile->setSplashPotion($item->getId() === Item::SPLASH_POTION); $tile->clearCustomColor(); $this->getLevel()->setBlock($this, $this, true); if($player->isSurvival()){ $player->getInventory()->setItemInHand(Item::get(Item::GLASS_BOTTLE)); } $color = Potion::getColor($item->getDamage()); $this->getLevel()->addSound(new SpellSound($this->add(0.5, 1, 0.5), $color[0], $color[1], $color[2])); } break; case Item::GLASS_BOTTLE: $player->getServer()->getPluginManager()->callEvent($ev = new PlayerGlassBottleEvent($player, $this, $item)); if($ev->isCancelled()){ return false; } if($this->meta < 2){ break; } if($tile->hasPotion()){ $this->meta -= 2; if($tile->getSplashPotion() === true){ $result = Item::get(Item::SPLASH_POTION, $tile->getPotionId()); }else{ $result = Item::get(Item::POTION, $tile->getPotionId()); } if($this->isEmpty()){ $tile->setPotionId(0xffff);//reset $tile->setSplashPotion(false); $tile->clearCustomColor(); } $this->getLevel()->setBlock($this, $this, true); $this->addItem($item, $player, $result); $color = Potion::getColor($result->getDamage()); $this->getLevel()->addSound(new SpellSound($this->add(0.5, 1, 0.5), $color[0], $color[1], $color[2])); }else{ $this->meta -= 2; $this->getLevel()->setBlock($this, $this, true); if($player->isSurvival()){ $result = Item::get(Item::POTION, Potion::WATER_BOTTLE); $this->addItem($item, $player, $result); } $this->getLevel()->addSound(new GraySplashSound($this->add(0.5, 1, 0.5))); } break; } return true; } /** * @param Item $item * @param Player $player * @param Item $result */ public function addItem(Item $item, Player $player, Item $result){ if($item->getCount() <= 1){ $player->getInventory()->setItemInHand($result); }else{ $item->setCount($item->getCount() - 1); if($player->getInventory()->canAddItem($result) === true){ $player->getInventory()->addItem($result); }else{ $motion = $player->getDirectionVector()->multiply(0.4); $position = clone $player->getPosition(); $player->getLevel()->dropItem($position->add(0, 0.5, 0), $result, $motion, 40); } } } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 2.5; } /** * @return string */ public function getName() : string{ return "Chest"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x + 0.0625, $this->y, $this->z + 0.0625, $this->x + 0.9375, $this->y + 0.9475, $this->z + 0.9375 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $chest = null; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; for($side = 2; $side <= 5; ++$side){ if(($this->meta === 4 or $this->meta === 5) and ($side === 4 or $side === 5)){ continue; }elseif(($this->meta === 3 or $this->meta === 2) and ($side === 2 or $side === 3)){ continue; } $c = $this->getSide($side); if($c instanceof Chest and $c->getDamage() === $this->meta){ $tile = $this->getLevel()->getTile($c); if($tile instanceof TileChest and !$tile->isPaired()){ $chest = $tile; break; } } } $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::CHEST), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } $tile = Tile::createTile("Chest", $this->getLevel(), $nbt); if($chest instanceof TileChest and $tile instanceof TileChest){ $chest->pairWith($tile); $tile->pairWith($chest); } return true; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ $t = $this->getLevel()->getTile($this); if($t instanceof TileChest){ $t->unpair(); } $this->getLevel()->setBlock($this, new Air(), true, true); return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $top = $this->getSide(1); if($top->isTransparent() !== true){ return true; } $t = $this->getLevel()->getTile($this); $chest = null; if($t instanceof TileChest){ $chest = $t; }else{ $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::CHEST), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); $chest = Tile::createTile("Chest", $this->getLevel(), $nbt); } if(isset($chest->namedtag->Lock) and $chest->namedtag->Lock instanceof StringTag){ if($chest->namedtag->Lock->getValue() !== $item->getCustomName()){ return true; } } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow($chest->getInventory()); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.4; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return string */ public function getName() : string{ return "Chorus Flower"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 0x07){ $drops[] = [Item::CHORUS_FRUIT, 0, 1]; } return $drops; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.4; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return string */ public function getName() : string{ return "Chorus Plant"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 0x07){ $drops[] = [Item::CHORUS_FRUIT, 0, 1]; } return $drops; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.6; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return string */ public function getName() : string{ return "Clay Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::CLAY, 0, 4], ]; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getBurnChance() : int{ return 5; } /** * @return int */ public function getBurnAbility() : int{ return 5; } /** * @return string */ public function getName() : string{ return "Coal Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::COAL_BLOCK, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Coal Ore"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::COAL_ORE, 0, 1], ]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $times = [1, 1, 2, 3, 4]; $time = $times[mt_rand(0, $fortunel + 1)]; return [ [Item::COAL, 0, $time], ]; } }else{ return []; } } } meta = $meta; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Cobblestone"; } /** * @return int */ public function getHardness(){ return 2; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::COBBLESTONE, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Cobblestone Stairs"; } }meta = $meta; } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @return string */ public function getName() : string{ return "Cobweb"; } /** * @return int */ public function getHardness(){ return 4; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHEARS; } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ $entity->resetFallDistance(); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isShears()){ return [ [Item::COBWEB, 0, 1], ]; }elseif($item->isSword() >= Tool::TIER_WOODEN){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::COBWEB, 0, 1], ]; }else{ return [ [Item::STRING, 0, 1], ]; } } return []; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Cocoa Block"; } /** * @return float */ public function getHardness(){ return 0.2; } /** * @return int */ public function getResistance(){ return 15; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ $block = clone $this; if($block->meta > 7){ return false; } $block->meta += 4; Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getNewState(), true, true); } $item->count--; return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $faces = [3, 4, 2, 5, 3, 4, 2, 5, 3, 4, 2, 5]; if($this->getSide($faces[$this->meta])->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if(mt_rand(0, 45) === 1){ if($this->meta <= 7){ $block = clone $this; $block->meta += 4; Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getNewState(), true, true); }else{ return Level::BLOCK_UPDATE_RANDOM; } } }else{ return Level::BLOCK_UPDATE_RANDOM; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($target->getId() === Block::WOOD and $target->getDamage() === 3){ if($face !== 0 and $face !== 1){ $faces = [ 2 => 0, 3 => 2, 4 => 3, 5 => 1, ]; $this->meta = $faces[$face]; $this->getLevel()->setBlock($block, Block::get(Item::COCOA_BLOCK, $this->meta), true); return true; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 8){ $drops[] = [Item::DYE, 3, 3]; }else{ $drops[] = [Item::DYE, 3, 1]; } return $drops; } } meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return string */ public function getName() : string{ return "Command Block"; } /** * @return int */ public function getHardness(){ return -1; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.8; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return mixed */ public function getName(){ static $names = [ 0 => "White Concrete", 1 => "Orange Concrete", 2 => "Magenta Concrete", 3 => "Light Blue Concrete", 4 => "Yellow Concrete", 5 => "Lime Concrete", 6 => "Pink Concrete", 7 => "Gray Concrete", 8 => "Silver Concrete", 9 => "Cyan Concrete", 10 => "Purple Concrete", 11 => "Blue Concrete", 12 => "Brown Concrete", 13 => "Green Concrete", 14 => "Red Concrete", 15 => "Black Concrete", ]; return $names[$this->meta & 0x0f]; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return float */ public function getResistance(){ return 2.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return mixed */ public function getName(){ static $names = [ 0 => "White Concrete Powder", 1 => "Orange Concrete Powder", 2 => "Magenta Concrete Powder", 3 => "Light Blue Concrete Powder", 4 => "Yellow Concrete Powder", 5 => "Lime Concrete Powder", 6 => "Pink Concrete Powder", 7 => "Gray Concrete Powder", 8 => "Silver Concrete Powder", 9 => "Cyan Concrete Powder", 10 => "Purple Concrete Powder", 11 => "Blue Concrete Powder", 12 => "Brown Concrete Powder", 13 => "Green Concrete Powder", 14 => "Red Concrete Powder", 15 => "Black Concrete Powder", ]; return $names[$this->meta & 0x0f]; } } getSide(0); if($down->getId() === self::FARMLAND){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal $block = clone $this; $block->meta += mt_rand(2, 5); if($block->meta > 7){ $block->meta = 7; } Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getNewState(), true, true); } $item->count--; return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if(mt_rand(0, 2) == 1){ if($this->meta < 0x07){ $block = clone $this; ++$block->meta; Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getNewState(), true, true); }else{ return Level::BLOCK_UPDATE_RANDOM; } } }else{ return Level::BLOCK_UPDATE_RANDOM; } } return false; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Cyan Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Dandelion"; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === 2 or $down->getId() === 3 or $down->getId() === 60){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Dark Oak Door Block"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::DARK_OAK_DOOR, 0, 1], ]; } }boundingBox === null){ $this->boundingBox = $this->recalculateBoundingBox(); } return $this->boundingBox; } /** * @return bool */ public function canBeFlowedInto(){ return false; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return DLDetector */ protected function getTile(){ $t = $this->getLevel()->getTile($this); if($t instanceof DLDetector){ return $t; }else{ $nbt = new CompoundTag("", [ new StringTag("id", Tile::DL_DETECTOR), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); return Tile::createTile(Tile::DL_DETECTOR, $this->getLevel(), $nbt); } } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ $this->getLevel()->setBlock($this, new DaylightDetectorInverted(), true, true); $this->getTile()->onUpdate(); return true; } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return $this->getTile()->isActivated(); } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ $this->getLevel()->setBlock($this, new Air()); if($this->isActivated()) $this->deactivate(); } /** * @return float */ public function getHardness(){ return 0.2; } /** * @return int */ public function getResistance(){ return 1; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [self::DAYLIGHT_SENSOR, 0, 1] ]; } }getLevel()->setBlock($this, new DaylightDetector(), true, true); $this->getTile()->onUpdate(); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Dead Bush"; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === Block::SAND or $down->getId() === Block::PODZOL or $down->getId() === Block::HARDENED_CLAY or $down->getId() === Block::STAINED_CLAY ){ $this->getLevel()->setBlock($block, $this, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isShears()){ return [ [Item::DEAD_BUSH, 0, 1], ]; }else{ return [ [Item::STICK, 0, mt_rand(0, 2)], ]; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Detector Rail"; } } meta = $meta; } /** * @return int */ public function getHardness(){ return 5; } /** * @return string */ public function getName() : string{ return "Diamond Block"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 4){ return [ [Item::DIAMOND_BLOCK, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getHardness(){ return 3; } /** * @return string */ public function getName() : string{ return "Diamond Ore"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 4){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::DIAMOND_ORE, 0, 1], ]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $times = [1, 1, 2, 3, 4]; $time = $times[mt_rand(0, $fortunel + 1)]; return [ [Item::DIAMOND, 0, $time], ]; } }else{ return []; } } } meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return string */ public function getName() : string{ return "Dirt"; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($item->isHoe()){ $item->useOn($this, 2); $this->getLevel()->setBlock($this, Block::get(Item::FARMLAND, 0), true); return true; } return false; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 3.5; } /** * @return string */ public function getName() : string{ return "Dispenser"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $dispenser = null; if($player instanceof Player){ $pitch = $player->getPitch(); if(abs($pitch) >= 45){ if($pitch < 0) $f = 4; else $f = 5; }else $f = $player->getDirection(); }else $f = 0; $faces = [ 3 => 3, 0 => 4, 2 => 5, 1 => 2, 4 => 0, 5 => 1 ]; $this->meta = $faces[$f]; $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::DISPENSER), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::DISPENSER, $this->getLevel(), $nbt); return true; } /** * */ public function activate(){ $tile = $this->getLevel()->getTile($this); if($tile instanceof TileDispenser){ $tile->activate(); } } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $t = $this->getLevel()->getTile($this); $dispenser = null; if($t instanceof TileDispenser){ $dispenser = $t; }else{ $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::DISPENSER), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); $dispenser = Tile::createTile(Tile::DISPENSER, $this->getLevel(), $nbt); } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow($dispenser->getInventory()); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } } getDamage(); $isUp = ($damage & 0x08) > 0; if($isUp){ $down = $this->getSide(Vector3::SIDE_DOWN)->getDamage(); $up = $damage; }else{ $down = $damage; $up = $this->getSide(Vector3::SIDE_UP)->getDamage(); } $isRight = ($up & 0x01) > 0; return $down & 0x07 | ($isUp ? 8 : 0) | ($isRight ? 0x10 : 0); } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ $f = 0.1875; $damage = $this->getFullDamage(); $bb = new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 2, $this->z + 1 ); $j = $damage & 0x03; $isOpen = (($damage & 0x04) > 0); $isRight = (($damage & 0x10) > 0); if($j === 0){ if($isOpen){ if(!$isRight){ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + $f ); }else{ $bb->setBounds( $this->x, $this->y, $this->z + 1 - $f, $this->x + 1, $this->y + 1, $this->z + 1 ); } }else{ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + $f, $this->y + 1, $this->z + 1 ); } }elseif($j === 1){ if($isOpen){ if(!$isRight){ $bb->setBounds( $this->x + 1 - $f, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); }else{ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + $f, $this->y + 1, $this->z + 1 ); } }else{ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + $f ); } }elseif($j === 2){ if($isOpen){ if(!$isRight){ $bb->setBounds( $this->x, $this->y, $this->z + 1 - $f, $this->x + 1, $this->y + 1, $this->z + 1 ); }else{ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + $f ); } }else{ $bb->setBounds( $this->x + 1 - $f, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); } }elseif($j === 3){ if($isOpen){ if(!$isRight){ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + $f, $this->y + 1, $this->z + 1 ); }else{ $bb->setBounds( $this->x + 1 - $f, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); } }else{ $bb->setBounds( $this->x, $this->y, $this->z + 1 - $f, $this->x + 1, $this->y + 1, $this->z + 1 ); } } return $bb; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(Vector3::SIDE_DOWN)->getId() === self::AIR and $this->getSide(Vector3::SIDE_UP) instanceof Door){ //Block underneath the door was broken $this->getLevel()->setBlock($this, new Air(), false, false); $this->getLevel()->setBlock($this->getSide(Vector3::SIDE_UP), new Air(), false); foreach($this->getDrops(Item::get(Item::DIAMOND_PICKAXE)) as $drop){ $this->getLevel()->dropItem($this, Item::get($drop[0], $drop[1], $drop[2])); } return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($face === 1){ $blockUp = $this->getSide(Vector3::SIDE_UP); $blockDown = $this->getSide(Vector3::SIDE_DOWN); if($blockUp->canBeReplaced() === false or $blockDown->isTransparent() === true){ return false; } $direction = $player instanceof Player ? $player->getDirection() : 0; $face = [ 0 => 3, 1 => 4, 2 => 2, 3 => 5, ]; $next = $this->getSide($face[(($direction + 2) % 4)]); $next2 = $this->getSide($face[$direction]); $metaUp = 0x08; if($next->getId() === $this->getId() or ($next2->isTransparent() === false and $next->isTransparent() === true)){ //Door hinge $metaUp |= 0x01; } $this->setDamage($player->getDirection() & 0x03); $this->getLevel()->setBlock($block, $this, true, true); //Bottom $this->getLevel()->setBlock($blockUp, $b = Block::get($this->getId(), $metaUp), true); //Top return true; } return false; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ if(($this->getDamage() & 0x08) === 0x08){ $down = $this->getSide(Vector3::SIDE_DOWN); if($down->getId() === $this->getId()){ $this->getLevel()->setBlock($down, new Air(), true); } }else{ $up = $this->getSide(Vector3::SIDE_UP); if($up->getId() === $this->getId()){ $this->getLevel()->setBlock($up, new Air(), true); } } $this->getLevel()->setBlock($this, new Air(), true); return true; } /** * @return bool */ public function isOpened(){ return (($this->getFullDamage() & 0x04) > 0); } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if(($this->getDamage() & 0x08) === 0x08){ //Top $down = $this->getSide(Vector3::SIDE_DOWN); if($down->getId() === $this->getId()){ $meta = $down->getDamage() ^ 0x04; $this->getLevel()->setBlock($down, Block::get($this->getId(), $meta), true); $players = $this->getLevel()->getChunkPlayers($this->x >> 4, $this->z >> 4); if($player instanceof Player){ unset($players[$player->getLoaderId()]); } $this->level->addSound(new DoorSound($this)); return true; } return false; }else{ $this->meta ^= 0x04; $this->getLevel()->setBlock($this, $this, true); $players = $this->getLevel()->getChunkPlayers($this->x >> 4, $this->z >> 4); if($player instanceof Player){ unset($players[$player->getLoaderId()]); } $this->level->addSound(new DoorSound($this)); } return true; } } meta = $meta; } /** * @return bool */ public function canBeReplaced(){ return true; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Sunflower", 1 => "Lilac", 2 => "Double Tallgrass", 3 => "Large Fern", 4 => "Rose Bush", 5 => "Peony" ]; return $names[$this->meta & 0x07]; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true && !$this->getSide(0) instanceof DoublePlant){ //Replace with common break method $this->getLevel()->setBlock($this, new Air(), false, false); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); $up = $this->getSide(1); if($down->getId() === self::GRASS or $down->getId() === self::DIRT){ $this->getLevel()->setBlock($block, $this, true); $this->getLevel()->setBlock($up, Block::get($this->id, $this->meta ^ 0x08), true); return true; } return false; } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ $up = $this->getSide(1); $down = $this->getSide(0); if(($this->meta & 0x08) === 0x08){ // This is the Top part of flower if($up->getId() === $this->id and $up->meta !== 0x08){ // Checks if the block ID and meta are right $this->getLevel()->setBlock($up, new Air(), true, true); }elseif($down->getId() === $this->id and $down->meta !== 0x08){ $this->getLevel()->setBlock($down, new Air(), true, true); } }else{ // Bottom Part of flower if($up->getId() === $this->id and ($up->meta & 0x08) === 0x08){ $this->getLevel()->setBlock($up, new Air(), true, true); }elseif($down->getId() === $this->id and ($down->meta & 0x08) === 0x08){ $this->getLevel()->setBlock($down, new Air(), true, true); } } } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if(($this->meta & 0x08) !== 0x08){ return [[Item::DOUBLE_PLANT, $this->meta, 1]]; }else{ return []; } } } isPickaxe() >= 1){ return [ [Item::RED_SANDSTONE_SLAB, $this->meta, 2], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Stone", 1 => "Sandstone", 2 => "Wooden", 3 => "Cobblestone", 4 => "Brick", 5 => "Stone Brick", 6 => "Quartz", 7 => "Nether Brick", ]; return "Double " . $names[$this->meta & 0x07] . " Slab"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::SLAB, $this->meta & 0x07, 2], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Oak", 1 => "Spruce", 2 => "Birch", 3 => "Jungle", 4 => "Acacia", 5 => "Dark Oak", 6 => "", 7 => "" ]; return "Double " . $names[$this->meta & 0x07] . " Wooden Slab"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::WOOD_SLAB, $this->meta & 0x07, 2], ]; } }meta = $meta; } /** * @return string */ public function getName(){ return "Dragon Egg"; } /** * @return int */ public function getHardness(){ return -1; } /** * @return int */ public function getResistance(){ return 18000000; } /** * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return false; } } meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 3.5; } /** * @return string */ public function getName() : string{ return "Dropper"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $dispenser = null; if($player instanceof Player){ $pitch = $player->getPitch(); if(abs($pitch) >= 45){ if($pitch < 0) $f = 4; else $f = 5; }else $f = $player->getDirection(); }else $f = 0; $faces = [ 3 => 3, 0 => 4, 2 => 5, 1 => 2, 4 => 0, 5 => 1 ]; $this->meta = $faces[$f]; $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::DROPPER), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::DROPPER, $this->getLevel(), $nbt); return true; } /** * */ public function activate(){ $tile = $this->getLevel()->getTile($this); if($tile instanceof TileDropper){ $tile->activate(); } } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $t = $this->getLevel()->getTile($this); $dropper = null; if($t instanceof TileDropper){ $dropper = $t; }else{ $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::DROPPER), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); $dropper = Tile::createTile(Tile::DROPPER, $this->getLevel(), $nbt); } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow($dropper->getInventory()); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } } meta = $meta; } /** * @return int */ public function getHardness(){ return 5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Emerald Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 4){ return [ [Item::EMERALD_BLOCK, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Emerald Ore"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 3; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 4){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::EMERALD_ORE, 0, 1], ]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $times = [1, 1, 2, 3, 4]; $time = $times[mt_rand(0, $fortunel + 1)]; return [ [Item::EMERALD, 0, $time], ]; } }else{ return []; } } } meta = $meta; } /** * @return int */ public function getLightLevel(){ return 12; } /** * @return AxisAlignedBB */ public function getBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.75, $this->z + 1 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::ENCHANT_TABLE), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::ENCHANT_TABLE, $this->getLevel(), $nbt); return true; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 5; } /** * @return int */ public function getResistance(){ return 6000; } /** * @return string */ public function getName() : string{ return "Enchanting Table"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if(!$this->getLevel()->getServer()->enchantingTableEnabled){ return true; } if($player instanceof Player){ if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $enchantTable = null; $this->getLevel()->setBlock($this, $this, true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::ENCHANT_TABLE), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::ENCHANT_TABLE, $this->getLevel(), $nbt); } $player->addWindow(new EnchantInventory($this)); $player->craftingType = Player::CRAFTING_ENCHANT; return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [$this->id, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getLightLevel(){ return 1; } /** * @return string */ public function getName() : string{ return "End Portal"; } /** * @return int */ public function getHardness(){ return -1; } /** * @return int */ public function getResistance(){ return 18000000; } /** * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return false; } }meta = $meta; } /** * @return int */ public function getLightLevel(){ return 1; } /** * @return string */ public function getName() : string{ return "End Portal Frame"; } /** * @return int */ public function getHardness(){ return -1; } /** * @return int */ public function getResistance(){ return 18000000; } /** * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return false; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + (($this->getDamage() & 0x04) > 0 ? 1 : 0.8125), $this->z + 1 ); } }meta = $meta; } /** * @return int */ public function getLightLevel(){ return 14; } /** * @return string */ public function getName(){ return "End Rod"; } /** * @return int */ public function getResistance(){ return 0; } /** * @return int */ public function getHardness(){ return 0; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 0, 1 => 1, 2 => 3, 3 => 2, 4 => 5, 5 => 4, ]; $this->meta = ($target->getId() === self::END_ROD && $faces[$face] == $target->getDamage()) ? Vector3::getOppositeSide($faces[$face]) : $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "End Stone"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 3; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.8; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "End Stone Bricks"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [self::END_STONE_BRICKS, $this->meta & 0x03, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 22.5; } /** * @return int */ public function getResistance(){ return 3000; } /** * @return int */ public function getLightLevel(){ return 7; } /** * @return string */ public function getName() : string{ return "Ender Chest"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x + 0.0625, $this->y, $this->z + 0.0625, $this->x + 0.9375, $this->y + 0.9475, $this->z + 0.9375 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::ENDER_CHEST), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } Tile::createTile("EnderChest", $this->getLevel(), $nbt); return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $top = $this->getSide(1); if($top->isTransparent() !== true){ return true; } if(!($this->getLevel()->getTile($this) instanceof TileEnderChest)){ $nbt = new CompoundTag("", [ new StringTag("id", Tile::ENDER_CHEST), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); Tile::createTile("EnderChest", $this->getLevel(), $nbt); } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->getEnderChestInventory()->openAt($this); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->hasEnchantment(Enchantment::TYPE_MINING_SILK_TOUCH)){ return [ [$this->id, 0, 1], ]; } return [ [Item::OBSIDIAN, 0, 8], ]; } }getLevel()->setBlock($this, $this, true, true); return $ret; } /** * @param int $type */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $down = $this->getSide(Vector3::SIDE_DOWN); if($down->getId() === self::AIR or ($down instanceof Liquid)){ $fall = Entity::createEntity("FallingSand", $this->getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x + 0.5), new DoubleTag("", $this->y), new DoubleTag("", $this->z + 0.5) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), "TileID" => new IntTag("TileID", $this->getId()), "Data" => new ByteTag("Data", $this->getDamage()), ])); $fall->spawnToAll(); } } } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Farmland"; } /** * @return float */ public function getHardness(){ return 0.6; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.9375, $this->z + 1 ); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::DIRT, 0, 1], ]; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return int */ public function getBurnChance() : int{ return 5; } /** * @return int */ public function getBurnAbility() : int{ return 20; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Oak Fence", 1 => "Spruce Fence", 2 => "Birch Fence", 3 => "Jungle Fence", 4 => "Acacia Fence", 5 => "Dark Oak Fence", "", "" ]; return $names[$this->meta & 0x07]; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ $north = $this->canConnect($this->getSide(Vector3::SIDE_NORTH)); $south = $this->canConnect($this->getSide(Vector3::SIDE_SOUTH)); $west = $this->canConnect($this->getSide(Vector3::SIDE_WEST)); $east = $this->canConnect($this->getSide(Vector3::SIDE_EAST)); $n = $north ? 0 : 0.375; $s = $south ? 1 : 0.625; $w = $west ? 0 : 0.375; $e = $east ? 1 : 0.625; return new AxisAlignedBB( $this->x + $w, $this->y, $this->z + $n, $this->x + $e, $this->y + 1.5, $this->z + $s ); } /** * @param Block $block * * @return bool */ public function canConnect(Block $block){ return ($block instanceof Fence or $block instanceof FenceGate) ? true : $block->isSolid() and !$block->isTransparent(); } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Oak Fence Gate"; } /** * @return int */ public function getHardness(){ return 2; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return null|AxisAlignedBB */ protected function recalculateBoundingBox(){ if(($this->getDamage() & 0x04) > 0){ return null; } $i = ($this->getDamage() & 0x03); if($i === 2 or $i === 0){ return new AxisAlignedBB( $this->x, $this->y, $this->z + 0.375, $this->x + 1, $this->y + 1.5, $this->z + 0.625 ); }else{ return new AxisAlignedBB( $this->x + 0.375, $this->y, $this->z, $this->x + 0.625, $this->y + 1.5, $this->z + 1 ); } } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->meta = ($player instanceof Player ? ($player->getDirection() - 1) & 0x03 : 0); $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @return bool */ public function isOpened(){ return (($this->getDamage() & 0x04) > 0); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ $this->meta = (($this->meta ^ 0x04) & ~0x02); if($player !== null){ $this->meta |= (($player->getDirection() - 1) & 0x02); } $this->getLevel()->setBlock($this, $this, true); $this->level->addSound(new DoorSound($this)); return true; } } meta = $meta; if($this->temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @return string */ public function getName() : string{ return "Fire Block"; } /** * @return int */ public function getLightLevel(){ return 15; } /** * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return false; } /** * @return bool */ public function canBeReplaced(){ return true; } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ $ProtectL = 0; if(!$entity->hasEffect(Effect::FIRE_RESISTANCE)){ $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1); if($entity->attack($ev->getFinalDamage(), $ev) === true){ $ev->useArmors(); } $ProtectL = $ev->getFireProtectL(); } $ev = new EntityCombustByBlockEvent($this, $entity, 8, $ProtectL); if($entity instanceof Arrow){ $ev->setCancelled(); } Server::getInstance()->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $entity->setOnFire($ev->getDuration()); } } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return []; } /** * @param int $type * * @return int */ public function onUpdate($type){ if($type == Level::BLOCK_UPDATE_NORMAL or $type == Level::BLOCK_UPDATE_RANDOM or $type == Level::BLOCK_UPDATE_SCHEDULED){ if(!$this->getSide(Vector3::SIDE_DOWN)->isTopFacingSurfaceSolid() and !$this->canNeighborBurn()){ $this->getLevel()->setBlock($this, new Air(), true); return Level::BLOCK_UPDATE_NORMAL; }elseif($type == Level::BLOCK_UPDATE_NORMAL or $type == Level::BLOCK_UPDATE_RANDOM){ $this->getLevel()->scheduleUpdate($this, $this->getTickRate() + mt_rand(0, 10)); }elseif($type == Level::BLOCK_UPDATE_SCHEDULED and $this->getLevel()->getServer()->fireSpread){ $forever = $this->getSide(Vector3::SIDE_DOWN)->getId() == Block::NETHERRACK; //TODO: END if(!$this->getSide(Vector3::SIDE_DOWN)->isTopFacingSurfaceSolid() and !$this->canNeighborBurn()){ $this->getLevel()->setBlock($this, new Air(), true); } if(!$forever and $this->getLevel()->getWeather()->isRainy() and ($this->getLevel()->canBlockSeeSky($this) or $this->getLevel()->canBlockSeeSky($this->getSide(Vector3::SIDE_EAST)) or $this->getLevel()->canBlockSeeSky($this->getSide(Vector3::SIDE_WEST)) or $this->getLevel()->canBlockSeeSky($this->getSide(Vector3::SIDE_SOUTH)) or $this->getLevel()->canBlockSeeSky($this->getSide(Vector3::SIDE_NORTH)) ) ){ $this->getLevel()->setBlock($this, new Air(), true); }else{ $meta = $this->meta; if($meta < 15){ $this->meta = $meta + mt_rand(0, 3); $this->getLevel()->setBlock($this, $this, true); } $this->getLevel()->scheduleUpdate($this, $this->getTickRate() + mt_rand(0, 10)); if(!$forever and !$this->canNeighborBurn()){ if(!$this->getSide(Vector3::SIDE_DOWN)->isTopFacingSurfaceSolid() or $meta > 3){ $this->getLevel()->setBlock($this, new Air(), true); } }elseif(!$forever && !($this->getSide(Vector3::SIDE_DOWN)->getBurnAbility() > 0) && $meta >= 15 && mt_rand(0, 4) == 0){ $this->getLevel()->setBlock($this, new Air(), true); }else{ $o = 0; //TODO: decrease the o if the rainfall values are high $this->tryToCatchBlockOnFire($this->getSide(Vector3::SIDE_EAST), 300 + $o, $meta); $this->tryToCatchBlockOnFire($this->getSide(Vector3::SIDE_WEST), 300 + $o, $meta); $this->tryToCatchBlockOnFire($this->getSide(Vector3::SIDE_DOWN), 250 + $o, $meta); $this->tryToCatchBlockOnFire($this->getSide(Vector3::SIDE_UP), 250 + $o, $meta); $this->tryToCatchBlockOnFire($this->getSide(Vector3::SIDE_SOUTH), 300 + $o, $meta); $this->tryToCatchBlockOnFire($this->getSide(Vector3::SIDE_NORTH), 300 + $o, $meta); for($x = ($this->x - 1); $x <= ($this->x + 1); ++$x){ for($z = ($this->z - 1); $z <= ($this->z + 1); ++$z){ for($y = ($this->y - 1); $y <= ($this->y + 4); ++$y){ $k = 100; if($y > $this->y + 1){ $k += ($y - ($this->y + 1)) * 100; } $chance = $this->getChanceOfNeighborsEncouragingFire($this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $z))); if($chance > 0){ $t = ($chance + 40 + $this->getLevel()->getServer()->getDifficulty() * 7); //TODO: decrease t if the rainfall values are high if($t > 0 and mt_rand(0, $k) <= $t){ $damage = min(15, $meta + mt_rand(0, 5) / 4); $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $z), new Fire($damage), true); $this->getLevel()->scheduleUpdate($this->temporalVector, $this->getTickRate()); } } } } } } } } } return 0; } /** * @return int */ public function getTickRate() : int{ return 30; } /*public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ for($s = 0; $s <= 5; ++$s){ $side = $this->getSide($s); if($side->getId() !== self::AIR and !($side instanceof Liquid)){ return false; } } $this->getLevel()->setBlock($this, new Air(), true); return Level::BLOCK_UPDATE_NORMAL; }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if($this->getSide(0)->getId() !== self::NETHERRACK){ $this->getLevel()->setBlock($this, new Air(), true); return Level::BLOCK_UPDATE_NORMAL; } } return false; }*/ /** * @param Block $block * @param int $bound * @param int $damage */ private function tryToCatchBlockOnFire(Block $block, int $bound, int $damage){ $burnAbility = $block->getBurnAbility(); if(mt_rand(0, $bound) < $burnAbility){ if(mt_rand(0, $damage + 10) < 5){ $meta = max(15, $damage + mt_rand(0, 4) / 4); $this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new BlockBurnEvent($block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($block, $fire = new Fire($meta), true); $this->getLevel()->scheduleUpdate($block, $fire->getTickRate()); } }else{ $this->getLevel()->setBlock($this, new Air(), true); } if($block instanceof TNT){ $block->prime(); } } } /** * @param Block $block * * @return int|mixed */ private function getChanceOfNeighborsEncouragingFire(Block $block){ if($block->getId() !== self::AIR){ return 0; }else{ $chance = 0; for($i = 0; $i < 5; $i++){ $chance = max($chance, $block->getSide($i)->getBurnChance()); } return $chance; } } } meta = $meta; } /** * @return string */ public function getName() : string{ static $names = [ self::TYPE_POPPY => "Poppy", self::TYPE_BLUE_ORCHID => "Blue Orchid", self::TYPE_ALLIUM => "Allium", self::TYPE_AZURE_BLUET => "Azure Bluet", self::TYPE_RED_TULIP => "Red Tulip", self::TYPE_ORANGE_TULIP => "Orange Tulip", self::TYPE_WHITE_TULIP => "White Tulip", self::TYPE_PINK_TULIP => "Pink Tulip", self::TYPE_OXEYE_DAISY => "Oxeye Daisy", 9 => "Unknown", 10 => "Unknown", 11 => "Unknown", 12 => "Unknown", 13 => "Unknown", 14 => "Unknown", 15 => "Unknown" ]; return $names[$this->meta]; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === Block::GRASS or $down->getId() === Block::DIRT or $down->getId() === Block::FARMLAND){ $this->getLevel()->setBlock($block, $this, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Flower Pot Block"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x + 0.3125, $this->y, $this->z + 0.3125, $this->x + 0.6875, $this->y + 0.375, $this->z + 0.6875 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($this->getSide(Vector3::SIDE_DOWN)->isTransparent()){ return false; } $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::FLOWER_POT), new IntTag("x", $block->x), new IntTag("y", $block->y), new IntTag("z", $block->z), new ShortTag("item", 0), new IntTag("mData", 0), ]); if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::FLOWER_POT, $this->getLevel(), $nbt); return true; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ $pot = $this->getLevel()->getTile($this); if(!($pot instanceof TileFlowerPot)){ return false; } if(!$pot->canAddItem($item)){ return true; } $this->setDamage(self::STATE_FULL); //specific damage value is unnecessary, it just needs to be non-zero to show an item. $this->getLevel()->setBlock($this, $this, true, false); $pot->setItem($item); if($player instanceof Player){ if($player->isSurvival()){ $item->setCount($item->getCount() - 1); $player->getInventory()->setItemInHand($item->getCount() > 0 ? $item : Item::get(Item::AIR)); } } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $items = [[Item::FLOWER_POT, 0, 1]]; $tile = $this->getLevel()->getTile($this); if($tile instanceof TileFlowerPot){ if(($item = $tile->getItem())->getId() !== Item::AIR){ $items[] = [$item->getId(), $item->getDamage(), 1]; } } return $items; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Glass"; } /** * @return float */ public function getHardness(){ return 0.3; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::GLASS, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Glass Pane"; } /** * @return float */ public function getHardness(){ return 0.3; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::GLASS_PANE, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Glowing Obsidian"; } /** * @return int */ public function getLightLevel(){ return 12; } }getLevel()->setBlock($this, Block::get(Item::REDSTONE_ORE, $this->meta), false, false); return Level::BLOCK_UPDATE_WEAK; } return false; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Glowstone"; } /** * @return float */ public function getHardness(){ return 0.3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getLightLevel(){ return 15; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::GLOWSTONE_BLOCK, 0, 1], ]; }else{ $fortuneL = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortuneL = $fortuneL > 3 ? 3 : $fortuneL; $times = [1, 1, 2, 3, 4]; $time = $times[mt_rand(0, $fortuneL + 1)]; $num = mt_rand(2, 4) * $time; $num = $num > 4 ? 4 : $num; return [ [Item::GLOWSTONE_DUST, 0, $num], ]; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Gold Block"; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 4){ return [ [Item::GOLD_BLOCK, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Gold Ore"; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 4){ return [ [Item::GOLD_ORE, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return string */ public function getName() : string{ return "Grass"; } /** * @return float */ public function getHardness(){ return 0.6; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::GRASS, 0, 1], ]; }else{ return [ [Item::DIRT, 0, 1], ]; } } /** * @param int $type */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_RANDOM){ $block = $this->getLevel()->getBlock(new Vector3($this->x, $this->y, $this->z)); if($block->getSide(1)->getLightLevel() < 4){ Server::getInstance()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($block, $this, new Dirt())); }elseif($block->getSide(1)->getLightLevel() >= 9){ for($l = 0; $l < 4; ++$l){ $x = mt_rand($this->x - 1, $this->x + 1); $y = mt_rand($this->y - 2, $this->y + 2); $z = mt_rand($this->z - 1, $this->z + 1); $block = $this->getLevel()->getBlock(new Vector3($x, $y, $z)); if($block->getId() === Block::DIRT && $block->getDamage() === 0x0F && $block->getSide(1)->getLightLevel() >= 4 && $block->z <= 2){ Server::getInstance()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($block, $this, new Grass())); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($block, $ev->getNewState()); } } } } } } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ $item->count--; TallGrassObject::growGrass($this->getLevel(), $this, new Random(mt_rand()), 8, 2); return true; }elseif($item->isHoe()){ $item->useOn($this); $this->getLevel()->setBlock($this, new Farmland()); return true; }elseif($item->isShovel() and $this->getSide(1)->getId() === Block::AIR){ $item->useOn($this); $this->getLevel()->setBlock($this, new GrassPath()); return true; } return false; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Grass Path"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.9375, $this->z + 1 ); } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type == Level::BLOCK_UPDATE_NORMAL){ $block = $this->getSide(self::SIDE_UP); if($block->getId() != self::AIR){ $this->getLevel()->setBlock($this, new Dirt(), true); } return Level::BLOCK_UPDATE_NORMAL; } return false; } /** * @return float */ public function getHardness(){ return 0.6; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::GRASS_PATH, 0, 1], ]; }else{ return [ [Item::DIRT, 0, 1], ]; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Gravel"; } /** * @return float */ public function getHardness(){ return 0.6; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){//使用精准采集附魔 不掉落燧石 $drops[] = [Item::GRAVEL, 0, 1]; return $drops; } $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $rates = [10, 7, 4, 1]; if(mt_rand(1, $rates[$fortunel]) === 1){//10% 14% 25% 100% $drops[] = [Item::FLINT, 0, 1]; } if(mt_rand(1, 10) !== 1){//90% $drops[] = [Item::GRAVEL, 0, 1]; } return $drops; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Gray Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Green Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Hardened Clay"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return float */ public function getHardness(){ return 1.25; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Hay Bale"; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return int */ public function getBurnChance() : int{ return 60; } /** * @return int */ public function getBurnAbility() : int{ return 20; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 0, 1 => 0, 2 => 0b1000, 3 => 0b1000, 4 => 0b0100, 5 => 0b0100, ]; $this->meta = ($this->meta & 0x03) | $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Hopper"; } /** * @return int */ public function getHardness(){ return 3; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $t = $this->getLevel()->getTile($this); if($t instanceof TileHopper){ if($t->hasLock() and !$t->checkLock($item->getCustomName())){ $player->getServer()->getLogger()->debug($player->getName() . " attempted to open a locked hopper"); return true; } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow($t->getInventory()); } } return true; } /** * */ public function activate(){ //TODO: Hopper content freezing (requires basic redstone system upgrade) } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 0, 1 => 0, 2 => 3, 3 => 2, 4 => 5, 5 => 4 ]; $this->meta = $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::HOPPER), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::HOPPER, $this->getLevel(), $nbt); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::HOPPER, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Ice"; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) === 0){ $this->getLevel()->setBlock($this, new Water(), true); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::ICE, 0, 1], ]; }else{ return []; } } } getLevel()->setBlock($this, new ActiveRedstoneLamp(), true, true); /*}else{ $this->getLevel()->setBlock($this, new ActiveRedstoneLamp(), true, false); //$this->lightAround(); }*/ return true; } public function turnOff(){ return true; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Invisible Bedrock"; } /** * @return int */ public function getHardness(){ return -1; } /** * @return int */ public function getResistance(){ return 18000000; } /** * @param Item $item * * @return bool */ public function isBreakable(Item $item){ return false; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Iron Block"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 5; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 3){ return [ [Item::IRON_BLOCK, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Iron Bars"; } /** * @return int */ public function getHardness(){ return 5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::IRON_BARS, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Iron Door Block"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 5; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::IRON_DOOR, 0, 1], ]; }else{ return []; } } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player) return true; else return parent::onActivate($item, $player); } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Iron Ore"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 3; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 3){ return [ [Item::IRON_ORE, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Item Frame"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if(!(($tile = $this->level->getTile($this)) instanceof TileItemFrame)){ $nbt = new CompoundTag("", [ new StringTag("id", Tile::ITEM_FRAME), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z), new FloatTag("ItemDropChance", 1.0), new ByteTag("ItemRotation", 0) ]); $tile = Tile::createTile(Tile::ITEM_FRAME, $this->getLevel(), $nbt); } if($tile->hasItem()){ $tile->setItemRotation(($tile->getItemRotation() + 1) % 8); $this->getLevel()->addSound(new ItemFrameRotateItemSound($this)); }else{ if($item->getCount() > 0){ $frameItem = clone $item; $frameItem->setCount(1); $item->setCount($item->getCount() - 1); $tile->setItem($frameItem); $this->getLevel()->addSound(new ItemFrameAddItemSound($this)); if($item->getId() === Item::FILLED_MAP){ $tile->SetMapID($item->getMapId()); } if($player instanceof Player and $player->isSurvival()){ $player->getInventory()->setItemInHand($item->getCount() <= 0 ? Item::get(Item::AIR) : $item); } } } return true; } /** * @param Item $item * * @return mixed */ public function onBreak(Item $item){ if(($tile = $this->level->getTile($this)) instanceof TileItemFrame){ //TODO: add events if(lcg_value() <= $tile->getItemDropChance() and $tile->getItem()->getId() !== Item::AIR){ $this->level->dropItem($tile->getBlock(), $tile->getItem()); } } return parent::onBreak($item); } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $sides = [ 0 => 4, 1 => 5, 2 => 2, 3 => 3 ]; if(!$this->getSide($sides[$this->meta])->isSolid()){ $this->level->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($face === 0 or $face === 1){ return false; } $faces = [ 2 => 3, 3 => 2, 4 => 1, 5 => 0 ]; $this->meta = $faces[$face]; $this->level->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::ITEM_FRAME), new IntTag("x", $block->x), new IntTag("y", $block->y), new IntTag("z", $block->z), new FloatTag("ItemDropChance", 1.0), new ByteTag("ItemRotation", 0) ]); if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::ITEM_FRAME, $this->getLevel(), $nbt); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::ITEM_FRAME, 0, 1] ]; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Jungle Door Block"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::JUNGLE_DOOR, 0, 1], ]; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Ladder"; } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @return bool */ public function isSolid(){ return false; } /** * @return float */ public function getHardness(){ return 0.4; } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ $entity->resetFallDistance(); $entity->onGround = true; } /** * @return null|AxisAlignedBB */ protected function recalculateBoundingBox(){ $f = 0.125; if($this->meta === 2){ return new AxisAlignedBB( $this->x, $this->y, $this->z + 1 - $f, $this->x + 1, $this->y + 1, $this->z + 1 ); }elseif($this->meta === 3){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + $f ); }elseif($this->meta === 4){ return new AxisAlignedBB( $this->x + 1 - $f, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); }elseif($this->meta === 5){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + $f, $this->y + 1, $this->z + 1 ); } return null; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($target->isTransparent() === false){ $faces = [ 2 => 2, 3 => 3, 4 => 4, 5 => 5, ]; if(isset($faces[$face])){ $this->meta = $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); return true; } } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ $faces = [ 2 => 3, 3 => 2, 4 => 5, 5 => 4, ]; /*if($this->getSide(0)->getId() === self::AIR){ //Replace with common break method Server::getInstance()->api->entity->drop($this, Item::get(LADDER, 0, 1)); $this->getLevel()->setBlock($this, new Air(), true, true, true); return Level::BLOCK_UPDATE_NORMAL; }*/ if($type === Level::BLOCK_UPDATE_NORMAL){ if(isset($faces[$this->meta])){ if($this->getSide($faces[$this->meta])->getId() === self::AIR){ $this->getLevel()->useBreakOn($this); } return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Lapis Lazuli Block"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 3; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 3){ return [ [Item::LAPIS_BLOCK, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Lapis Lazuli Ore"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 3){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::LAPIS_ORE, 0, 1], ]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $times = [1, 1, 2, 3, 4]; $time = $times[mt_rand(0, $fortunel + 1)]; return [ [Item::DYE, 4, mt_rand(4, 8) * $time], ]; } }else{ return []; } } } meta = $meta; } /** * @return int */ public function getLightLevel(){ return 15; } /** * @return string */ public function getName() : string{ return "Lava"; } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ $entity->fallDistance *= 0.5; $ProtectL = 0; if(!$entity->hasEffect(Effect::FIRE_RESISTANCE)){ $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_LAVA, 4); if($entity->attack($ev->getFinalDamage(), $ev) === true){ $ev->useArmors(); } $ProtectL = $ev->getFireProtectL(); } $ev = new EntityCombustByBlockEvent($this, $entity, 15, $ProtectL); Server::getInstance()->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $entity->setOnFire($ev->getDuration()); } $entity->resetFallDistance(); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $ret = $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->scheduleUpdate($this, $this->tickRate()); return $ret; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 0.2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHEARS; } /** * @return int */ public function getBurnChance() : int{ return 30; } /** * @return int */ public function getBurnAbility() : int{ return 60; } /** * @return string */ public function getName() : string{ static $names = [ self::OAK => "Oak Leaves", self::SPRUCE => "Spruce Leaves", self::BIRCH => "Birch Leaves", self::JUNGLE => "Jungle Leaves", ]; return $names[$this->meta & 0x03]; } /** * @param Block $pos * @param array $visited * @param $distance * @param $check * @param null $fromSide * * @return bool */ private function findLog(Block $pos, array $visited, $distance, &$check, $fromSide = null){ ++$check; $index = $pos->x . "." . $pos->y . "." . $pos->z; if(isset($visited[$index])){ return false; } if($pos->getId() === static::WOOD_TYPE){ return true; }elseif($pos->getId() === $this->id and $distance < 3){ $visited[$index] = true; $down = $pos->getSide(0)->getId(); if($down === static::WOOD_TYPE){ return true; } if($fromSide === null){ for($side = 2; $side <= 5; ++$side){ if($this->findLog($pos->getSide($side), $visited, $distance + 1, $check, $side) === true){ return true; } } }else{ //No more loops switch($fromSide){ case 2: if($this->findLog($pos->getSide(2), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(4), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(5), $visited, $distance + 1, $check, $fromSide) === true){ return true; } break; case 3: if($this->findLog($pos->getSide(3), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(4), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(5), $visited, $distance + 1, $check, $fromSide) === true){ return true; } break; case 4: if($this->findLog($pos->getSide(2), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(3), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(4), $visited, $distance + 1, $check, $fromSide) === true){ return true; } break; case 5: if($this->findLog($pos->getSide(2), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(3), $visited, $distance + 1, $check, $fromSide) === true){ return true; }elseif($this->findLog($pos->getSide(5), $visited, $distance + 1, $check, $fromSide) === true){ return true; } break; } } } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if(($this->meta & 0b00001100) === 0){ $this->meta |= 0x08; $this->getLevel()->setBlock($this, $this, false, false, true); } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if(($this->meta & 0b00001100) === 0x08){ $this->meta &= 0x03; $visited = []; $check = 0; Server::getInstance()->getPluginManager()->callEvent($ev = new LeavesDecayEvent($this)); if($ev->isCancelled() or $this->findLog($this, $visited, 0, $check) === true){ $this->getLevel()->setBlock($this, $this, false, false); }else{ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool|void */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->meta |= 0x04; $this->getLevel()->setBlock($this, $this, true); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($item->isShears() or $item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ $drops[] = [$this->id, $this->meta & 0x03, 1]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = min(3, $fortunel); $rates = [20, 16, 12, 10]; if(mt_rand(1, $rates[$fortunel]) === 1){ //Saplings $drops[] = [Item::SAPLING, $this->meta & 0x03, 1]; } $rates = [200, 180, 160, 120]; if(($this->meta & 0x03) === self::OAK and mt_rand(1, $rates[$fortunel]) === 1){ //Apples $drops[] = [Item::APPLE, 0, 1]; } } return $drops; } } meta = $meta; } /** * @return string */ public function getName() : string{ static $names = [ self::ACACIA => "Acacia Leaves", self::DARK_OAK => "Dark Oak Leaves", ]; return $names[$this->meta & 0x01]; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($item->isShears() or $item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ $drops[] = [$this->id, $this->meta & 0x01, 1]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = min(3, $fortunel); $rates = [20, 16, 12, 10]; if(mt_rand(1, $rates[$fortunel]) === 1){ //Saplings $drops[] = [Item::SAPLING, ($this->meta & 0x01) | 0x04, 1]; } } return $drops; } } meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return string */ public function getName() : string{ return "Lever"; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $side = $this->getDamage(); if($this->isActivated()) $side ^= 0x08; $faces = [ 5 => 0, 6 => 0, 3 => 2, 1 => 4, 4 => 3, 2 => 5, 0 => 1, 7 => 1, ]; $block = $this->getSide($faces[$side]); if($block->isTransparent()){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($target->isTransparent() === false){ $faces = [ 3 => 3, 2 => 4, 4 => 2, 5 => 1, ]; if($face === 0){ $to = $player instanceof Player ? $player->getDirection() : 0; $this->meta = ($to % 2 != 1 ? 0 : 7); }elseif($face === 1){ $to = $player instanceof Player ? $player->getDirection() : 0; $this->meta = ($to % 2 != 1 ? 6 : 5); }else{ $this->meta = $faces[$face]; } $this->getLevel()->setBlock($block, $this, true, false); return true; } return false; } /** * @param array $ignore * * @return bool|void */ public function activate(array $ignore = []){ parent::activate($ignore); $side = $this->meta; if($this->isActivated()) $side ^= 0x08; $faces = [ 5 => 0, 6 => 0, 3 => 2, 1 => 4, 4 => 3, 2 => 5, 0 => 1, 7 => 1, ]; $block = $this->getSide($faces[$side])->getSide(Vector3::SIDE_UP); if(!$this->equals($block)){ $this->activateBlock($block); } $this->checkTorchOn($this->getSide($faces[$side]), [static::getOppositeSide($faces[$side])]); } /** * @param array $ignore * * @return bool|void */ public function deactivate(array $ignore = []){ parent::deactivate($ignore); $side = $this->meta; if($this->isActivated()) $side ^= 0x08; $faces = [ 5 => 0, 6 => 0, 3 => 2, 1 => 4, 4 => 3, 2 => 5, 0 => 1, 7 => 1, ]; $block = $this->getSide($faces[$side])->getSide(Vector3::SIDE_UP); if(!$this->equals($block)){ $this->deactivateBlock($block); } $this->checkTorchOff($this->getSide($faces[$side]), [static::getOppositeSide($faces[$side])]); } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ $this->meta ^= 0x08; $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->addSound(new ButtonClickSound($this)); if($this->isActivated()) $this->activate(); else $this->deactivate(); return true; } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ if($this->isActivated()){ $this->meta ^= 0x08; $this->getLevel()->setBlock($this, $this, true, false); $this->deactivate(); } $this->getLevel()->setBlock($this, new Air(), true, false); } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return (($this->meta & 0x08) === 0x08); } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return float */ public function getResistance(){ return 2.5; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Light Blue Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Lime Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta; if($d >= 8){ $d = 0; } return ($d + 1) / 9; } /** * @param Vector3 $pos * * @return int */ protected function getFlowDecay(Vector3 $pos){ if(!($pos instanceof Block)){ $pos = $this->getLevel()->getBlock($pos); } if($pos->getId() !== $this->getId()){ return -1; }else{ return $pos->getDamage(); } } /** * @param Vector3 $pos * * @return int */ protected function getEffectiveFlowDecay(Vector3 $pos){ if(!($pos instanceof Block)){ $pos = $this->getLevel()->getBlock($pos); } if($pos->getId() !== $this->getId()){ return -1; } $decay = $pos->getDamage(); if($decay >= 8){ $decay = 0; } return $decay; } /** * @return Vector3 */ public function getFlowVector(){ $vector = new Vector3(0, 0, 0); if($this->temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } $decay = $this->getEffectiveFlowDecay($this); for($j = 0; $j < 4; ++$j){ $x = $this->x; $y = $this->y; $z = $this->z; if($j === 0){ --$x; }elseif($j === 1){ ++$x; }elseif($j === 2){ --$z; }elseif($j === 3){ ++$z; } $sideBlock = $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $z)); $blockDecay = $this->getEffectiveFlowDecay($sideBlock); if($blockDecay < 0){ if(!$sideBlock->canBeFlowedInto()){ continue; } $blockDecay = $this->getEffectiveFlowDecay($this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y - 1, $z))); if($blockDecay >= 0){ $realDecay = $blockDecay - ($decay - 8); $vector->x += ($sideBlock->x - $this->x) * $realDecay; $vector->y += ($sideBlock->y - $this->y) * $realDecay; $vector->z += ($sideBlock->z - $this->z) * $realDecay; } continue; }else{ $realDecay = $blockDecay - $decay; $vector->x += ($sideBlock->x - $this->x) * $realDecay; $vector->y += ($sideBlock->y - $this->y) * $realDecay; $vector->z += ($sideBlock->z - $this->z) * $realDecay; } } if($this->getDamage() >= 8){ $falling = false; if(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x, $this->y, $this->z - 1))->canBeFlowedInto()){ $falling = true; }elseif(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x, $this->y, $this->z + 1))->canBeFlowedInto()){ $falling = true; }elseif(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x - 1, $this->y, $this->z))->canBeFlowedInto()){ $falling = true; }elseif(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x + 1, $this->y, $this->z))->canBeFlowedInto()){ $falling = true; }elseif(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x, $this->y + 1, $this->z - 1))->canBeFlowedInto()){ $falling = true; }elseif(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x, $this->y + 1, $this->z + 1))->canBeFlowedInto()){ $falling = true; }elseif(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x - 1, $this->y + 1, $this->z))->canBeFlowedInto()){ $falling = true; }elseif(!$this->getLevel()->getBlock($this->temporalVector->setComponents($this->x + 1, $this->y + 1, $this->z))->canBeFlowedInto()){ $falling = true; } if($falling){ $vector = $vector->normalize()->add(0, -6, 0); } } return $vector->normalize(); } /** * @param Entity $entity * @param Vector3 $vector */ public function addVelocityToEntity(Entity $entity, Vector3 $vector){ $flow = $this->getFlowVector(); $vector->x += $flow->x; $vector->y += $flow->y; $vector->z += $flow->z; } /** * @return int */ public function tickRate() : int{ if($this instanceof Water){ return 5; }elseif($this instanceof Lava){ return 30; } return 0; } /** * @param int $type */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $this->checkForHarden(); $this->getLevel()->scheduleUpdate($this, $this->tickRate()); }elseif($type === Level::BLOCK_UPDATE_SCHEDULED){ if($this->temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } $decay = $this->getFlowDecay($this); $multiplier = $this instanceof Lava ? 2 : 1; $flag = true; if($decay > 0){ $smallestFlowDecay = -100; $this->adjacentSources = 0; $smallestFlowDecay = $this->getSmallestFlowDecay($this->level->getBlock($this->temporalVector->setComponents($this->x, $this->y, $this->z - 1)), $smallestFlowDecay); $smallestFlowDecay = $this->getSmallestFlowDecay($this->level->getBlock($this->temporalVector->setComponents($this->x, $this->y, $this->z + 1)), $smallestFlowDecay); $smallestFlowDecay = $this->getSmallestFlowDecay($this->level->getBlock($this->temporalVector->setComponents($this->x - 1, $this->y, $this->z)), $smallestFlowDecay); $smallestFlowDecay = $this->getSmallestFlowDecay($this->level->getBlock($this->temporalVector->setComponents($this->x + 1, $this->y, $this->z)), $smallestFlowDecay); $k = $smallestFlowDecay + $multiplier; if($k >= 8 or $smallestFlowDecay < 0){ $k = -1; } if(($topFlowDecay = $this->getFlowDecay($this->level->getBlock($this->level->getBlock($this->temporalVector->setComponents($this->x, $this->y + 1, $this->z))))) >= 0){ if($topFlowDecay >= 8){ $k = $topFlowDecay; }else{ $k = $topFlowDecay | 0x08; } } if($this->adjacentSources >= 2 and $this instanceof Water){ $bottomBlock = $this->level->getBlock($this->level->getBlock($this->temporalVector->setComponents($this->x, $this->y - 1, $this->z))); if($bottomBlock->isSolid()){ $k = 0; }elseif($bottomBlock instanceof Water and $bottomBlock->getDamage() === 0){ $k = 0; } } if($this instanceof Lava and $decay < 8 and $k < 8 and $k > 1 and mt_rand(0, 4) !== 0){ $k = $decay; $flag = false; } if($k !== $decay){ $decay = $k; if($decay < 0){ $this->getLevel()->setBlock($this, new Air(), true); }else{ $this->getLevel()->setBlock($this, Block::get($this->id, $decay), true); $this->getLevel()->scheduleUpdate($this, $this->tickRate()); } }elseif($flag){ //$this->getLevel()->scheduleUpdate($this, $this->tickRate()); //$this->updateFlow(); } }else{ //$this->updateFlow(); } $bottomBlock = $this->level->getBlock($this->temporalVector->setComponents($this->x, $this->y - 1, $this->z)); if($bottomBlock->canBeFlowedInto() or $bottomBlock instanceof Liquid){ if($this instanceof Lava and $bottomBlock instanceof Water){ $this->getLevel()->setBlock($bottomBlock, Block::get(Item::STONE), true); $this->triggerLavaMixEffects($bottomBlock); return; } if($decay >= 8){ //$this->getLevel()->setBlock($bottomBlock, Block::get($this->id, $decay), true); //$this->getLevel()->scheduleUpdate($bottomBlock, $this->tickRate()); $this->flowIntoBlock($bottomBlock, $decay); }else{ //$this->getLevel()->setBlock($bottomBlock, Block::get($this->id, $decay + 8), true); //$this->getLevel()->scheduleUpdate($bottomBlock, $this->tickRate()); $this->flowIntoBlock($bottomBlock, $decay | 0x08); } }elseif($decay >= 0 and ($decay === 0 or !$bottomBlock->canBeFlowedInto())){ $flags = $this->getOptimalFlowDirections(); $l = $decay + $multiplier; if($decay >= 8){ $l = 1; } if($l >= 8){ $this->checkForHarden(); return; } if($flags[0]){ $this->flowIntoBlock($this->level->getBlock($this->temporalVector->setComponents($this->x - 1, $this->y, $this->z)), $l); } if($flags[1]){ $this->flowIntoBlock($this->level->getBlock($this->temporalVector->setComponents($this->x + 1, $this->y, $this->z)), $l); } if($flags[2]){ $this->flowIntoBlock($this->level->getBlock($this->temporalVector->setComponents($this->x, $this->y, $this->z - 1)), $l); } if($flags[3]){ $this->flowIntoBlock($this->level->getBlock($this->temporalVector->setComponents($this->x, $this->y, $this->z + 1)), $l); } } $this->checkForHarden(); } } /** * @param Block $block * @param $newFlowDecay */ private function flowIntoBlock(Block $block, $newFlowDecay){ if($block->canBeFlowedInto()){ if($block instanceof Lava){ $this->triggerLavaMixEffects($block); }elseif($block->getId() > 0){ $this->getLevel()->useBreakOn($block); } $this->getLevel()->setBlock($block, Block::get($this->getId(), $newFlowDecay), true); $this->getLevel()->scheduleUpdate($block, $this->tickRate()); } } /** * @param Block $block * @param $accumulatedCost * @param $previousDirection * * @return int */ private function calculateFlowCost(Block $block, $accumulatedCost, $previousDirection){ $cost = 1000; for($j = 0; $j < 4; ++$j){ if( ($j === 0 and $previousDirection === 1) or ($j === 1 and $previousDirection === 0) or ($j === 2 and $previousDirection === 3) or ($j === 3 and $previousDirection === 2) ){ $x = $block->x; $y = $block->y; $z = $block->z; if($j === 0){ --$x; }elseif($j === 1){ ++$x; }elseif($j === 2){ --$z; }elseif($j === 3){ ++$z; } $blockSide = $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $z)); if(!$blockSide->canBeFlowedInto() and !($blockSide instanceof Liquid)){ continue; }elseif($blockSide instanceof Liquid and $blockSide->getDamage() === 0){ continue; }elseif($this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y - 1, $z))->canBeFlowedInto()){ return $accumulatedCost; } if($accumulatedCost >= 4){ continue; } $realCost = $this->calculateFlowCost($blockSide, $accumulatedCost + 1, $j); if($realCost < $cost){ $cost = $realCost; } } } return $cost; } /** * @return int */ public function getHardness(){ return 100; } /** * @return array */ private function getOptimalFlowDirections(){ if($this->temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } for($j = 0; $j < 4; ++$j){ $this->flowCost[$j] = 1000; $x = $this->x; $y = $this->y; $z = $this->z; if($j === 0){ --$x; }elseif($j === 1){ ++$x; }elseif($j === 2){ --$z; }elseif($j === 3){ ++$z; } $block = $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $z)); if(!$block->canBeFlowedInto() and !($block instanceof Liquid)){ continue; }elseif($block instanceof Liquid and $block->getDamage() === 0){ continue; }elseif($this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y - 1, $z))->canBeFlowedInto()){ $this->flowCost[$j] = 0; }else{ $this->flowCost[$j] = $this->calculateFlowCost($block, 1, $j); } } $minCost = $this->flowCost[0]; for($i = 1; $i < 4; ++$i){ if($this->flowCost[$i] < $minCost){ $minCost = $this->flowCost[$i]; } } for($i = 0; $i < 4; ++$i){ $this->isOptimalFlowDirection[$i] = ($this->flowCost[$i] === $minCost); } return $this->isOptimalFlowDirection; } /** * @param Vector3 $pos * @param $decay * * @return int */ private function getSmallestFlowDecay(Vector3 $pos, $decay){ $blockDecay = $this->getFlowDecay($pos); if($blockDecay < 0){ return $decay; }elseif($blockDecay === 0){ ++$this->adjacentSources; }elseif($blockDecay >= 8){ $blockDecay = 0; } return ($decay >= 0 && $blockDecay >= $decay) ? $decay : $blockDecay; } private function checkForHarden(){ if($this instanceof Lava){ $colliding = false; for($side = 0; $side <= 5 and !$colliding; ++$side){ $colliding = $this->getSide($side) instanceof Water; } if($colliding){ if($this->getDamage() === 0){ $this->getLevel()->setBlock($this, Block::get(Item::OBSIDIAN), true); }elseif($this->getDamage() <= 4){ $this->getLevel()->setBlock($this, Block::get(Item::COBBLESTONE), true); } $this->triggerLavaMixEffects($this); } } } /** * @return null */ public function getBoundingBox(){ return null; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return []; } /** * Creates fizzing sound and smoke. Used when lava flows over block or mixes with water. * * @param Vector3 $pos */ protected function triggerLavaMixEffects(Vector3 $pos){ $this->getLevel()->addSound(new FizzSound($pos->add(0.5, 0.5, 0.5), 2.5 + mt_rand(0, 1000) / 1000 * 0.8)); for($i = 0; $i < 8; ++$i){ $this->getLevel()->addParticle(new SmokeParticle($pos->add(mt_rand(0, 80) / 100, 0.5, mt_rand(0, 80) / 100))); } } } meta = $meta; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($player instanceof Player){ $this->meta = ((int) $player->getDirection() + 5) % 4; } $this->getLevel()->setBlock($block, $this, true, true); if($player != null){ $level = $this->getLevel(); if($player->getServer()->allowSnowGolem){ $block0 = $level->getBlock($block->add(0, -1, 0)); $block1 = $level->getBlock($block->add(0, -2, 0)); if($block0->getId() == Item::SNOW_BLOCK and $block1->getId() == Item::SNOW_BLOCK){ $level->setBlock($block, new Air()); $level->setBlock($block0, new Air()); $level->setBlock($block1, new Air()); $golem = new SnowGolem($player->getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x), new DoubleTag("", $this->y), new DoubleTag("", $this->z) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), ])); $golem->spawnToAll(); } } if($player->getServer()->allowIronGolem){ $block0 = $level->getBlock($block->add(0, -1, 0)); $block1 = $level->getBlock($block->add(0, -2, 0)); $block2 = $level->getBlock($block->add(-1, -1, 0)); $block3 = $level->getBlock($block->add(1, -1, 0)); $block4 = $level->getBlock($block->add(0, -1, -1)); $block5 = $level->getBlock($block->add(0, -1, 1)); if($block0->getId() == Item::IRON_BLOCK and $block1->getId() == Item::IRON_BLOCK){ if($block2->getId() == Item::IRON_BLOCK and $block3->getId() == Item::IRON_BLOCK and $block4->getId() == Item::AIR and $block5->getId() == Item::AIR){ $level->setBlock($block2, new Air()); $level->setBlock($block3, new Air()); }elseif($block4->getId() == Item::IRON_BLOCK and $block5->getId() == Item::IRON_BLOCK and $block2->getId() == Item::AIR and $block3->getId() == Item::AIR){ $level->setBlock($block4, new Air()); $level->setBlock($block5, new Air()); }else return true; $level->setBlock($block, new Air()); $level->setBlock($block0, new Air()); $level->setBlock($block1, new Air()); $golem = new IronGolem($player->getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x), new DoubleTag("", $this->y), new DoubleTag("", $this->z) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), ])); $golem->spawnToAll(); } } } return true; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Lit Redstone Lamp"; } /** * @return float */ public function getHardness(){ return 0.3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::REDSTONE_LAMP, 0, 1], ]; } /** * @return bool */ public function turnOn(){ $this->meta = 0; $this->getLevel()->setBlock($this, $this, true, false); return true; } /** * @return bool */ public function turnOff(){ $this->getLevel()->setBlock($this, new RedstoneLamp(), true, true); return true; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Magenta Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Melon Block"; } /** * @return int */ public function getHardness(){ return 1; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::MELON_BLOCK, 0, 1], ]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 2 ? 2 : $fortunel; //Note: for Melon level 2 is the same 3 So highest is 2 return [ [Item::MELON_SLICE, 0, mt_rand(3, 7 + $fortunel)], ]; } } } meta = $meta; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if(mt_rand(0, 2) == 1){ if($this->meta < 0x07){ $block = clone $this; ++$block->meta; Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getNewState(), true); } return Level::BLOCK_UPDATE_RANDOM; }else{ for($side = 2; $side <= 5; ++$side){ $b = $this->getSide($side); if($b->getId() === self::MELON_BLOCK){ return Level::BLOCK_UPDATE_RANDOM; } } $side = $this->getSide(mt_rand(2, 5)); $d = $side->getSide(0); if($side->getId() === self::AIR and ($d->getId() === self::FARMLAND or $d->getId() === self::GRASS or $d->getId() === self::DIRT)){ Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($side, new Melon())); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($side, $ev->getNewState(), true); } } } } return Level::BLOCK_UPDATE_RANDOM; } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::MELON_SEEDS, 0, mt_rand(0, 2)], ]; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 1; } /** * @return string */ public function getName() : string{ return "Mob Head"; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x + 0.25, $this->y, $this->z + 0.25, $this->x + 0.75, $this->y + 0.5, $this->z + 0.75 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($face !== 0){ $this->meta = $face; if($face === 1){ $rot = floor(($player->yaw * 16 / 360) + 0.5) & 0x0F; }else{ $rot = $face; } $this->getLevel()->setBlock($block, $this, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::SKULL), new ByteTag("SkullType", $item->getDamage()), new ByteTag("Rot", $rot), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } /** @var Spawnable $tile */ Tile::createTile("Skull", $this->getLevel(), $nbt); return true; } return false; } /** * @param int $type * * @return int|void */ public function onUpdate($type){ $faces = [ 1 => 0, 2 => 3, 3 => 2, 4 => 5, 5 => 4, ]; if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide($faces[$this->meta])->getId() === self::AIR){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return parent::onUpdate($type); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $tile = $this->level->getTile($this); if($tile instanceof SkullTile){ return [ [Item::MOB_HEAD, $tile->getType(), 1] ]; } return []; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Monster Spawner"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($this->getDamage() == 0){ if($item->getId() == Item::SPAWN_EGG){ $tile = $this->getLevel()->getTile($this); if($tile instanceof MobSpawner){ $this->meta = $item->getDamage(); //$this->getLevel()->setBlock($this, $this, true, false); $tile->setEntityId($this->meta); } return true; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new StringTag("id", Tile::MOB_SPAWNER), new IntTag("x", $block->x), new IntTag("y", $block->y), new IntTag("z", $block->z), new IntTag("EntityId", 0), ]); if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } Tile::createTile(Tile::MOB_SPAWNER, $this->getLevel(), $nbt); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return []; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Moss Stone"; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::MOSS_STONE, $this->meta, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Mycelium"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return float */ public function getHardness(){ return 0.6; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::MYCELIUM, 0, 1], ]; }else{ return [ [Item::DIRT, 0, 1], ]; } } /** * @param int $type */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_RANDOM){ //TODO: light levels $x = mt_rand($this->x - 1, $this->x + 1); $y = mt_rand($this->y - 2, $this->y + 2); $z = mt_rand($this->z - 1, $this->z + 1); $block = $this->getLevel()->getBlock(new Vector3($x, $y, $z)); if($block->getId() === Block::DIRT){ if($block->getSide(1) instanceof Transparent){ Server::getInstance()->getPluginManager()->callEvent($ev = new BlockSpreadEvent($block, $this, new Mycelium())); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($block, $ev->getNewState()); } } } } } } meta = $meta; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Nether Bricks"; } /** * @return int */ public function getHardness(){ return 2; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::NETHER_BRICKS, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getToolType(){ //Different then the woodfences return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Nether Brick Fence"; } /** * @param Block $block * * @return bool */ public function canConnect(Block $block){ return ($block instanceof NetherBrickFence) or ($block->isSolid() and !$block->isTransparent()); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= Tool::TIER_WOODEN){ return [ [Item::NETHER_BRICK_FENCE, $this->meta, 1], ]; }else{ return []; } } }meta = $meta; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Nether Quartz Ore"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getResistance(){ return 15; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= Tool::TIER_WOODEN){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::NETHER_QUARTZ_ORE, 0, 1], ]; }else{ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $times = [1, 1, 2, 3, 4]; $time = $times[mt_rand(0, $fortunel + 1)]; return [ [Item::NETHER_QUARTZ, 0, $time], ]; } }else{ return []; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Nether Reactor"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Nether Wart Block"; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === self::SOUL_SAND){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if(mt_rand(0, 12) == 1){//only have 0-3 So maybe slowly if($this->meta < 0x03){//0x03 $block = clone $this; ++$block->meta; Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getNewState(), true, true); }else{ return Level::BLOCK_UPDATE_RANDOM; } } }else{ return Level::BLOCK_UPDATE_RANDOM; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 0x03){ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $drops[] = [Item::NETHER_WART, 0, mt_rand(2, 4 + $fortunel)]; }else{ $drops[] = [Item::NETHER_WART, 0, 1]; } return $drops; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 0.8; } /** * @return int */ public function getResistance(){ return 4; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getStrength(){ if($this->meta < 24) $this->meta++; else $this->meta = 0; $this->getLevel()->setBlock($this, $this); return $this->meta * 1; } /** * @return int */ public function getInstrument(){ $below = $this->getSide(Vector3::SIDE_DOWN); switch($below->getId()){ case Block::WOOD: case Block::WOOD2: case Block::WOODEN_PLANK: case Block::WOODEN_SLABS: case Block::DOUBLE_WOOD_SLABS: case Block::OAK_WOODEN_STAIRS: case Block::SPRUCE_WOODEN_STAIRS: case Block::BIRCH_WOODEN_STAIRS: case Block::JUNGLE_WOODEN_STAIRS: case Block::ACACIA_WOODEN_STAIRS: case Block::DARK_OAK_WOODEN_STAIRS: case Block::FENCE: case Block::FENCE_GATE: case Block::FENCE_GATE_SPRUCE: case Block::FENCE_GATE_BIRCH: case Block::FENCE_GATE_JUNGLE: case Block::FENCE_GATE_DARK_OAK: case Block::FENCE_GATE_ACACIA: case Block::SPRUCE_WOOD_STAIRS: case Block::BOOKSHELF: case Block::CHEST: case Block::CRAFTING_TABLE: case Block::SIGN_POST: case Block::WALL_SIGN: case Block::DOOR_BLOCK: case Block::NOTEBLOCK: return NoteblockSound::INSTRUMENT_BASS; case Block::SAND: case Block::SOUL_SAND: return NoteblockSound::INSTRUMENT_TABOUR; case Block::GLASS: case Block::GLASS_PANE: return NoteblockSound::INSTRUMENT_CLICK; case Block::STONE: case Block::COBBLESTONE: case Block::SANDSTONE: case Block::MOSS_STONE: case Block::BRICKS: case Block::STONE_BRICK: case Block::NETHER_BRICKS: case Block::QUARTZ_BLOCK: case Block::SLAB: case Block::COBBLESTONE_STAIRS: case Block::BRICK_STAIRS: case Block::STONE_BRICK_STAIRS: case Block::NETHER_BRICKS_STAIRS: case Block::SANDSTONE_STAIRS: case Block::QUARTZ_STAIRS: case Block::COBBLESTONE_WALL: case Block::NETHER_BRICK_FENCE: case Block::BEDROCK: case Block::GOLD_ORE: case Block::IRON_ORE: case Block::COAL_ORE: case Block::LAPIS_ORE: case Block::DIAMOND_ORE: case Block::REDSTONE_ORE: case Block::GLOWING_REDSTONE_ORE: case Block::EMERALD_ORE: case Block::FURNACE: case Block::BURNING_FURNACE: case Block::OBSIDIAN: case Block::MONSTER_SPAWNER: case Block::NETHERRACK: case Block::ENCHANTING_TABLE: case Block::END_STONE: case Block::STAINED_CLAY: case Block::COAL_BLOCK: return NoteblockSound::INSTRUMENT_BASS_DRUM; } return NoteblockSound::INSTRUMENT_PIANO; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ $up = $this->getSide(Vector3::SIDE_UP); if($up->getId() == 0){ $this->getLevel()->addSound(new NoteblockSound($this, $this->getInstrument(), $this->getStrength())); return true; }else{ return false; } } /** * @return string */ public function getName() : string{ return "Noteblock"; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Netherrack"; } /** * @return float */ public function getHardness(){ return 0.4; } /** * @return int */ public function getResistance(){ return 2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::NETHERRACK, 0, 1], ]; }else{ return []; } } } meta = $meta; if($this->temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } } /** * @return string */ public function getName() : string{ return "Obsidian"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 35; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 5){ return [ [Item::OBSIDIAN, 0, 1], ]; }else{ return []; } } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ parent::onBreak($item); if($this->getLevel()->getServer()->netherEnabled){ for($i = 0; $i <= 6; $i++){ if($this->getSide($i)->getId() == self::PORTAL){ break; } if($i == 6){ return; } } $block = $this->getSide($i); if($this->getLevel()->getBlock($this->temporalVector->setComponents($block->x - 1, $block->y, $block->z))->getId() == Block::PORTAL or $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x + 1, $block->y, $block->z))->getId() == Block::PORTAL ){//x方向 for($x = $block->x; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $block->y, $block->z))->getId() == Block::PORTAL; $x++){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } } for($x = $block->x - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $block->y, $block->z))->getId() == Block::PORTAL; $x--){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } } }else{//z方向 for($z = $block->z; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $block->y, $z))->getId() == Block::PORTAL; $z++){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } } for($z = $block->z - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $block->y, $z))->getId() == Block::PORTAL; $z--){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } } } } } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Orange Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Packed Ice"; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::PACKED_ICE, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Pink Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return int */ public function getBurnChance() : int{ return 5; } /** * @return int */ public function getBurnAbility() : int{ return 20; } /** * @return string */ public function getName() : string{ static $names = [ self::OAK => "Oak Wood Planks", self::SPRUCE => "Spruce Wood Planks", self::BIRCH => "Birch Wood Planks", self::JUNGLE => "Jungle Wood Planks", self::ACACIA => "Acacia Wood Planks", self::DARK_OAK => "Dark Oak Wood Planks", 6 => "Unknown Planks", 7 => "Unknown Planks" ]; return $names[$this->meta & 0x07]; } }meta = $meta; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return string */ public function getName() : string{ return "Podzol"; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return float */ public function getResistance(){ return 2.5; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::PODZOL, 0, 1], ]; }else{ return [ [Item::DIRT, 0, 1], ]; } } } meta = $meta; if($this->temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } } /** * @return string */ public function getName() : string{ return "Portal"; } /** * @return int */ public function getHardness(){ return -1; } /** * @return int */ public function getResistance(){ return 0; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return bool */ public function canPassThrough(){ return true; } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ $block = $this; if($this->getLevel()->getBlock($this->temporalVector->setComponents($block->x - 1, $block->y, $block->z))->getId() == Block::PORTAL or $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x + 1, $block->y, $block->z))->getId() == Block::PORTAL ){//x方向 for($x = $block->x; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $block->y, $block->z))->getId() == Block::PORTAL; $x++){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } } for($x = $block->x - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $block->y, $block->z))->getId() == Block::PORTAL; $x--){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($x, $y, $block->z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($x, $y, $block->z), new Air()); } } }else{//z方向 for($z = $block->z; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $block->y, $z))->getId() == Block::PORTAL; $z++){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } } for($z = $block->z - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $block->y, $z))->getId() == Block::PORTAL; $z--){ for($y = $block->y; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y++){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } for($y = $block->y - 1; $this->getLevel()->getBlock($this->temporalVector->setComponents($block->x, $y, $z))->getId() == Block::PORTAL; $y--){ $this->getLevel()->setBlock($this->temporalVector->setComponents($block->x, $y, $z), new Air()); } } } parent::onBreak($item); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($player instanceof Player){ $this->meta = $player->getDirection() & 0x01; } $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return []; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Potato Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 0x07){ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $drops[] = [Item::POTATO, 0, mt_rand(1, 4 + $fortunel)]; }else{ $drops[] = [Item::POTATO, 0, 1]; } return $drops; } } meta = $meta;//0,1,2,3,4,5 } /** * @return string */ public function getName() : string{ return "PoweredRail"; } /** * @return bool */ protected function update(){ return true; } /** * @param Rail $block * * @return bool */ public function canConnect(Rail $block){ if($this->distanceSquared($block) > 2){ return false; } /** @var Vector3 [] $blocks */ if(count($blocks = self::check($this)) == 2){ return false; } if(isset($blocks[0])){ $v3 = $blocks[0]->subtract($this); $v33 = $block->subtract($this); if(abs($v3->x) == abs($v33->z) and abs($v3->z) == abs($v33->x)){ return false; } } return $blocks; } /** * @param Block $block * * @return bool|Block */ public function isBlock(Block $block){ if($block instanceof Air){ return false; } return $block; } /** * @param Rail $rail * @param bool $force * * @return bool */ public function connect(Rail $rail, $force = false){ if(!$force){ $connected = $this->canConnect($rail); if(!is_array($connected)){ return false; } /** @var Vector3 [] $connected */ $connected[] = $rail; switch(count($connected)){ case 1: $v3 = $connected[0]->subtract($this); $this->meta = (($v3->y != 1) ? ($v3->x == 0 ? 0 : 1) : ($v3->z == 0 ? ($v3->x / -2) + 2.5 : ($v3->z / 2) + 4.5)); break; case 2: $subtract = []; foreach($connected as $key => $value){ $subtract[$key] = $value->subtract($this); } if(abs($subtract[0]->x) == abs($subtract[1]->z) and abs($subtract[1]->x) == abs($subtract[0]->z)){ $v3 = $connected[0]->subtract($this)->add($connected[1]->subtract($this)); $this->meta = $v3->x == 1 ? ($v3->z == 1 ? 6 : 9) : ($v3->z == 1 ? 7 : 8); }elseif($subtract[0]->y == 1 or $subtract[1]->y == 1){ $v3 = $subtract[0]->y == 1 ? $subtract[0] : $subtract[1]; $this->meta = $v3->x == 0 ? ($v3->x == -1 ? 4 : 5) : ($v3->x == 1 ? 2 : 3); }else{ $this->meta = $subtract[0]->x == 0 ? 0 : 1; } break; default: break; } } $this->level->setBlock($this, Block::get($this->id, $this->meta), true, true); return true; } /** * @param Item $item * @param Block $block * @param Block $target * @param $face * @param $fx * @param $fy * @param $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $downBlock = $this->getSide(Vector3::SIDE_DOWN); if($downBlock instanceof Rail or !$this->isBlock($downBlock)){//判断是否可以放置 return false; } $arrayXZ = [[1, 0], [0, 1], [-1, 0], [0, -1]]; $arrayY = [0, 1, -1]; /** @var Vector3 [] $connected */ $connected = []; foreach($arrayXZ as $key => $xz){ foreach($arrayY as $y){ $v3 = (new Vector3($xz[0], $y, $xz[1]))->add($this); $block = $this->level->getBlock($v3); if($block instanceof Rail){ if($block->connect($this)){ $connected[] = $v3; //感觉这里怪怪的 if($key <= 1){ $xz = $arrayXZ[$key + 1]; foreach($arrayY as $yy){ $v3 = (new Vector3($xz[0], $yy, $xz[1]))->add($this); $block = $this->level->getBlock($v3); if($block instanceof Rail){ if($block->connect($this)){ $connected[] = $v3; } } } } break; } } } if(count($connected) >= 1){ break; } } switch(count($connected)){ case 1: $v3 = $connected[0]->subtract($this); $this->meta = (($v3->y != 1) ? ($v3->x == 0 ? 0 : 1) : ($v3->z == 0 ? ($v3->x / -2) + 2.5 : ($v3->z / 2) + 4.5)); break; case 2: $subtract = []; foreach($connected as $key => $value){ $subtract[$key] = $value->subtract($this); } if(abs($subtract[0]->x) == abs($subtract[1]->z) and abs($subtract[1]->x) == abs($subtract[0]->z)){ $v3 = $connected[0]->subtract($this)->add($connected[1]->subtract($this)); $this->meta = $v3->x == 1 ? ($v3->z == 1 ? 6 : 9) : ($v3->z == 1 ? 7 : 8); }elseif($subtract[0]->y == 1 or $subtract[1]->y == 1){ $v3 = $subtract[0]->y == 1 ? $subtract[0] : $subtract[1]; $this->meta = $v3->x == 0 ? ($v3->x == -1 ? 4 : 5) : ($v3->x == 1 ? 2 : 3); }else{ $this->meta = $subtract[0]->x == 0 ? 0 : 1; } break; default: break; } $this->level->setBlock($this, Block::get($this->id, $this->meta), true, true); return true; } /** * @return float */ public function getHardness(){ return 0.7; } /** * @return bool */ public function canPassThrough(){ return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Powered Repeater"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getStrength(){ return 15; } /** * @return int */ public function getDirection() : int{ $direction = 0; switch($this->meta % 4){ case 0: $direction = 3; break; case 1: $direction = 4; break; case 2: $direction = 2; break; case 3: $direction = 5; break; } return $direction; } /** * @return int */ public function getOppositeDirection() : int{ return static::getOppositeSide($this->getDirection()); } /** * @return int */ public function getDelayLevel() : int{ return round(($this->meta - ($this->meta % 4)) / 4) + 1; } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ if(!$from instanceof Block){ return false; }else{ if($this->y != $from->y){ return false; } if($from->equals($this->getSide($this->getOppositeDirection()))){ return true; } return false; } } /** * @param array $ignore * * @return bool|void */ public function activate(array $ignore = []){ if($this->canCalc()){ if($this->id != self::POWERED_REPEATER_BLOCK){ $this->id = self::POWERED_REPEATER_BLOCK; $this->getLevel()->setBlock($this, $this, true, false); } $this->getLevel()->setBlockTempData($this, self::ACTION_ACTIVATE); $this->getLevel()->scheduleUpdate($this, $this->getDelayLevel() * 2); } } /** * @param array $ignore * * @return bool|void */ public function deactivate(array $ignore = []){ if($this->canCalc()){ if($this->id != self::UNPOWERED_REPEATER_BLOCK){ $this->id = self::UNPOWERED_REPEATER_BLOCK; $this->getLevel()->setBlock($this, $this, true, false); } $this->getLevel()->setBlockTempData($this, self::ACTION_DEACTIVATE); $this->getLevel()->scheduleUpdate($this, $this->getDelayLevel() * 2); } } public function deactivateImmediately(){ $this->deactivateBlock($this->getSide($this->getOppositeDirection())); $this->deactivateBlock($this->getSide(Vector3::SIDE_DOWN, 2));//TODO: improve } /** * @param int $type * * @return int */ public function onUpdate($type){ if($type == Level::BLOCK_UPDATE_SCHEDULED){ if($this->getLevel()->getBlockTempData($this) == self::ACTION_ACTIVATE){ $this->activateBlock($this->getSide($this->getOppositeDirection())); $this->activateBlock($this->getSide(Vector3::SIDE_DOWN, 2)); }elseif($this->getLevel()->getBlockTempData($this) == self::ACTION_DEACTIVATE){ $this->deactivateImmediately(); } $this->getLevel()->setBlockTempData($this); } return $type; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ $meta = $this->meta + 4; if($meta > 15) $this->meta = $this->meta % 4; else $this->meta = $meta; $this->getLevel()->setBlock($this, $this, true, false); return true; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool|void */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($player instanceof Player){ $this->meta = ((int) $player->getDirection() + 5) % 4; } $this->getLevel()->setBlock($block, $this, true, false); if($this->checkPower($this)){ $this->activate(); } } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ $this->deactivateImmediately(); $this->getLevel()->setBlock($this, new Air(), true, false); $this->getLevel()->setBlockTempData($this); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::REPEATER, 0, 1] ]; } } meta = $meta; } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ if($this->getLevel()->getServer()->redstoneEnabled and $this->canActivate){ if(!$this->isActivated()){ $this->meta = 1; $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->addSound(new GenericSound($this, 1000)); } if(!$this->isActivated() or ($this->isActivated() and ($this->getLevel()->getServer()->getTick() % 30) == 0)){ $this->activate(); } } } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return ($this->meta == 0) ? false : true; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $below = $this->getSide(Vector3::SIDE_DOWN); if($below instanceof Transparent){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } /*if($type == Level::BLOCK_UPDATE_SCHEDULED){ if($this->isActivated()){ if(!$this->isCollided()){ $this->meta = 0; $this->getLevel()->setBlock($this, $this, true, false); $this->deactivate(); return Level::BLOCK_UPDATE_SCHEDULED; } } }*/ return true; } public function checkActivation(){ if($this->isActivated()){ if((($this->getLevel()->getServer()->getTick() - $this->activateTime)) >= 3){ $this->meta = 0; $this->getLevel()->setBlock($this, $this, true, false); $this->deactivate(); } } } /*public function isCollided(){ foreach($this->getLevel()->getEntities() as $p){ $blocks = $p->getBlocksAround(); if(isset($blocks[Level::blockHash($this->x, $this->y, $this->z)])) return true; } return false; }*/ /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool|void */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $below = $this->getSide(Vector3::SIDE_DOWN); if($below instanceof Transparent) return; else $this->getLevel()->setBlock($block, $this, true, false); } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ if($this->isActivated()){ $this->meta = 0; $this->deactivate(); } $this->canActivate = false; $this->getLevel()->setBlock($this, new Air(), true); } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return float */ public function getResistance(){ return 2.5; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.5; } /** * @return string */ public function getName() : string{ static $names = [ self::NORMAL => "Prismarine", self::DARK => "Dark Prismarine", self::BRICKS => "Prismarine Bricks", ]; return $names[$this->meta & 0x03] ?? "Unknown"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= Tool::TIER_WOODEN){ return [ [$this->id, $this->meta & 0x03, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getHardness(){ return 1; } /** * @return bool */ public function isHelmet(){ return true; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return string */ public function getName() : string{ return "Pumpkin"; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($player instanceof Player){ $this->meta = ((int) $player->getDirection() + 5) % 4; } $this->getLevel()->setBlock($block, $this, true, true); if($player != null){ $level = $this->getLevel(); if($player->getServer()->allowSnowGolem){ $block0 = $level->getBlock($block->add(0, -1, 0)); $block1 = $level->getBlock($block->add(0, -2, 0)); if($block0->getId() == Item::SNOW_BLOCK and $block1->getId() == Item::SNOW_BLOCK){ $level->setBlock($block, new Air()); $level->setBlock($block0, new Air()); $level->setBlock($block1, new Air()); $golem = new SnowGolem($player->getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x), new DoubleTag("", $this->y), new DoubleTag("", $this->z) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), ])); $golem->spawnToAll(); } } if($player->getServer()->allowIronGolem){ $block0 = $level->getBlock($block->add(0, -1, 0)); $block1 = $level->getBlock($block->add(0, -2, 0)); $block2 = $level->getBlock($block->add(-1, -1, 0)); $block3 = $level->getBlock($block->add(1, -1, 0)); $block4 = $level->getBlock($block->add(0, -1, -1)); $block5 = $level->getBlock($block->add(0, -1, 1)); if($block0->getId() == Item::IRON_BLOCK and $block1->getId() == Item::IRON_BLOCK){ if($block2->getId() == Item::IRON_BLOCK and $block3->getId() == Item::IRON_BLOCK and $block4->getId() == Item::AIR and $block5->getId() == Item::AIR){ $level->setBlock($block2, new Air()); $level->setBlock($block3, new Air()); }elseif($block4->getId() == Item::IRON_BLOCK and $block5->getId() == Item::IRON_BLOCK and $block2->getId() == Item::AIR and $block3->getId() == Item::AIR){ $level->setBlock($block4, new Air()); $level->setBlock($block5, new Air()); }else return false; $level->setBlock($block, new Air()); $level->setBlock($block0, new Air()); $level->setBlock($block1, new Air()); $golem = new IronGolem($player->getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x), new DoubleTag("", $this->y), new DoubleTag("", $this->z) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), ])); $golem->spawnToAll(); } } } return true; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Pumpkin Stem"; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent()){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if(mt_rand(0, 2) == 1){ if($this->meta < 0x07){ $block = clone $this; ++$block->meta; Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($this, $block)); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($this, $ev->getNewState(), true); } return Level::BLOCK_UPDATE_RANDOM; }else{ for($side = 2; $side <= 5; ++$side){ $b = $this->getSide($side); if($b->getId() === self::PUMPKIN){ return Level::BLOCK_UPDATE_RANDOM; } } $side = $this->getSide(mt_rand(2, 5)); $d = $side->getSide(0); if($side->getId() === self::AIR and ($d->getId() === self::FARMLAND or $d->getId() === self::GRASS or $d->getId() === self::DIRT)){ Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($side, new Pumpkin())); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($side, $ev->getNewState(), true); } } } } return Level::BLOCK_UPDATE_RANDOM; } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::PUMPKIN_SEEDS, 0, mt_rand(0, 2)], ]; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Purple Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Purpur Block", 2 => "Purpur Pillar", ]; return $names[$this->meta & 0x0f] ?? "Purpur Block"; //TODO fix properly; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= Tool::TIER_WOODEN){ return [ [$this->id, $this->meta & 0x0f, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Purpur Stairs"; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 0.8; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Quartz Block", 1 => "Chiseled Quartz Block", 2 => "Quartz Pillar", 3 => "Quartz Block", ]; return $names[$this->meta & 0x03]; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($this->meta === 1 or $this->meta === 2){ //Quartz pillar block and chiselled quartz have different orientations $faces = [ 0 => 0, 1 => 0, 2 => 0b1000, 3 => 0b1000, 4 => 0b0100, 5 => 0b0100, ]; $this->meta = ($this->meta & 0x03) | $faces[$face]; } $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::QUARTZ_BLOCK, $this->meta & 0x03, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.8; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Quartz Stairs"; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Rail"; } /** * @return bool */ protected function update(){ return true; } /** * @param Rail $block * * @return bool */ public function canConnect(Rail $block){ if($this->distanceSquared($block) > 2){ return false; } /** @var Vector3 [] $blocks */ if(count($blocks = self::check($this)) == 2){ return false; } return $blocks; } /** * @param Block $block * * @return bool|Block */ public function isBlock(Block $block){ if($block instanceof Air){ return false; } return $block; } /** * @param Rail $rail * @param bool $force * * @return bool */ public function connect(Rail $rail, $force = false){ if(!$force){ $connected = $this->canConnect($rail); if(!is_array($connected)){ return false; } /** @var Vector3 [] $connected */ $connected[] = $rail; switch(count($connected)){ case 1: $v3 = $connected[0]->subtract($this); $this->meta = (($v3->y != 1) ? ($v3->x == 0 ? 0 : 1) : ($v3->z == 0 ? ($v3->x / -2) + 2.5 : ($v3->z / 2) + 4.5)); break; case 2: $subtract = []; foreach($connected as $key => $value){ $subtract[$key] = $value->subtract($this); } if(abs($subtract[0]->x) == abs($subtract[1]->z) and abs($subtract[1]->x) == abs($subtract[0]->z)){ $v3 = $connected[0]->subtract($this)->add($connected[1]->subtract($this)); $this->meta = $v3->x == 1 ? ($v3->z == 1 ? 6 : 9) : ($v3->z == 1 ? 7 : 8); }elseif($subtract[0]->y == 1 or $subtract[1]->y == 1){ $v3 = $subtract[0]->y == 1 ? $subtract[0] : $subtract[1]; $this->meta = $v3->x == 0 ? ($v3->z == -1 ? 4 : 5) : ($v3->x == 1 ? 2 : 3); }else{ $this->meta = $subtract[0]->x == 0 ? 0 : 1; } break; default: break; } } $this->level->setBlock($this, Block::get($this->id, $this->meta), true, true); return true; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $downBlock = $this->getSide(Vector3::SIDE_DOWN); if($downBlock instanceof Rail or !$this->isBlock($downBlock)){//判断是否可以放置 return false; } $arrayXZ = [[1, 0], [0, 1], [-1, 0], [0, -1]]; $arrayY = [0, 1, -1]; /** @var Vector3 [] $connected */ $connected = []; foreach($arrayXZ as $xz){ $x = $xz[0]; $z = $xz[1]; foreach($arrayY as $y){ $v3 = (new Vector3($x, $y, $z))->add($this); $block = $this->level->getBlock($v3); if($block instanceof Rail){ if($block->connect($this)){ $connected[] = $v3; break; } } } if(count($connected) == 2){ break; } } switch(count($connected)){ case 1: $v3 = $connected[0]->subtract($this); $this->meta = (($v3->y != 1) ? ($v3->x == 0 ? 0 : 1) : ($v3->z == 0 ? ($v3->x / -2) + 2.5 : ($v3->z / 2) + 4.5)); break; case 2: $subtract = []; foreach($connected as $key => $value){ $subtract[$key] = $value->subtract($this); } if(abs($subtract[0]->x) == abs($subtract[1]->z) and abs($subtract[1]->x) == abs($subtract[0]->z)){ $v3 = $connected[0]->subtract($this)->add($connected[1]->subtract($this)); $this->meta = $v3->x == 1 ? ($v3->z == 1 ? 6 : 9) : ($v3->z == 1 ? 7 : 8); }elseif($subtract[0]->y == 1 or $subtract[1]->y == 1){ $v3 = $subtract[0]->y == 1 ? $subtract[0] : $subtract[1]; $this->meta = $v3->x == 0 ? ($v3->z == -1 ? 4 : 5) : ($v3->x == 1 ? 2 : 3); }else{ $this->meta = $subtract[0]->x == 0 ? 0 : 1; } break; default: break; } $this->level->setBlock($this, Block::get($this->id, $this->meta), true, true); return true; } /** * @param Rail $rail * * @return array */ public static function check(Rail $rail){ $array = [ [[0, 1], [0, -1]], [[1, 0], [-1, 0]], [[1, 0], [-1, 0]], [[1, 0], [-1, 0]], [[0, 1], [0, -1]], [[0, 1], [0, -1]], [[1, 0], [0, 1]], [[0, 1], [-1, 0]], [[-1, 0], [0, -1]], [[0, -1], [1, 0]] ]; $arrayY = [0, 1, -1]; $blocks = $array[$rail->getDamage()]; $connected = []; foreach($arrayY as $y){ $v3 = new Vector3($rail->x + $blocks[0][0], $rail->y + $y, $rail->z + $blocks[0][1]); $id = $rail->getLevel()->getBlockIdAt($v3->x, $v3->y, $v3->z); $meta = $rail->getLevel()->getBlockDataAt($v3->x, $v3->y, $v3->z); if(in_array($id, [self::RAIL, self::ACTIVATOR_RAIL, self::DETECTOR_RAIL, self::POWERED_RAIL]) and in_array([$rail->x - $v3->x, $rail->z - $v3->z], $array[$meta])){ $connected[] = $v3; break; } } foreach($arrayY as $y){ $v3 = new Vector3($rail->x + $blocks[1][0], $rail->y + $y, $rail->z + $blocks[1][1]); $id = $rail->getLevel()->getBlockIdAt($v3->x, $v3->y, $v3->z); $meta = $rail->getLevel()->getBlockDataAt($v3->x, $v3->y, $v3->z); if(in_array($id, [self::RAIL, self::ACTIVATOR_RAIL, self::DETECTOR_RAIL, self::POWERED_RAIL]) and in_array([$rail->x - $v3->x, $rail->z - $v3->z], $array[$meta])){ $connected[] = $v3; break; } } return $connected; } /** * @return float */ public function getHardness(){ return 0.7; } /** * @return float */ public function getResistance(){ return 3.5; } /** * @return bool */ public function canPassThrough(){ return true; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Red Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Red Mushroom"; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->isTransparent() === false){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return string */ public function getName() : string{ return "Red Mushroom Block"; } /** * @return float */ public function getHardness(){ return 0.2; } /** * @return int */ public function getResistance(){ return 1; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::RED_MUSHROOM_BLOCK, self::RED, 1], ]; }else{ return [ [Item::RED_MUSHROOM, 0, mt_rand(0, 2)], ]; } } } "Red Sandstone", 1 => "Chiseled Red Sandstone", 2 => "Smooth Red Sandstone", 3 => "", ]; return $names[$this->meta & 0x03]; } }getId() === self::RED_SANDSTONE_SLAB and ($target->getDamage() & 0x08) === 0x08){ $this->getLevel()->setBlock($target, Block::get(Item::DOUBLE_RED_SANDSTONE_SLAB, $this->meta), true); return true; }elseif($block->getId() === self::RED_SANDSTONE_SLAB){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_RED_SANDSTONE_SLAB, $this->meta), true); return true; }else{ $this->meta |= 0x08; } }elseif($face === 1){ if($target->getId() === self::RED_SANDSTONE_SLAB and ($target->getDamage() & 0x08) === 0){ $this->getLevel()->setBlock($target, Block::get(Item::DOUBLE_RED_SANDSTONE_SLAB, $this->meta), true); return true; }elseif($block->getId() === self::RED_SANDSTONE_SLAB){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_RED_SANDSTONE_SLAB, $this->meta), true); return true; } //TODO: check for collision }else{ if($block->getId() === self::RED_SANDSTONE_SLAB){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_RED_SANDSTONE_SLAB, $this->meta), true); }else{ if($fy > 0.5){ $this->meta |= 0x08; } } } if($block->getId() === self::RED_SANDSTONE_SLAB and ($target->getDamage() & 0x07) !== ($this->meta & 0x07)){ return false; } $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return \pocketmine\math\AxisAlignedBB */ public function getBoundingBox(){ return Block::getBoundingBox(); } /** * @return bool */ public function canBeFlowedInto(){ return false; } /** * @return bool */ public function isSolid(){ return true; } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return true; } /** * @return int */ public function getHardness(){ return 5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Block of Redstone"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::REDSTONE_BLOCK, 0, 1], ]; }else{ return []; } } } meta = $meta; } /** * @return int */ public function getLightLevel(){ return 0; } /** * @return string */ public function getName() : string{ return "Redstone Lamp"; } /** * @return bool */ public function turnOn(){ $this->getLevel()->setBlock($this, new LitRedstoneLamp(), true, true); return true; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Redstone Ore"; } /** * @return int */ public function getHardness(){ return 3; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL or $type === Level::BLOCK_UPDATE_TOUCH){ $this->getLevel()->setBlock($this, Block::get(Item::GLOWING_REDSTONE_ORE, $this->meta)); return Level::BLOCK_UPDATE_WEAK; } return false; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= Tool::TIER_IRON){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::REDSTONE_ORE, 0, 1], ]; }else{ $fortuneL = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortuneL = $fortuneL > 3 ? 3 : $fortuneL; return [ [Item::REDSTONE_DUST, 0, mt_rand(4, 5 + $fortuneL)], ]; } }else{ return []; } } } meta = $meta; } /** * @return int */ public function getMaxStrength(){ return $this->maxStrength; } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return $this->activated; } /** * @return bool */ public function canCalc(){ return $this->getLevel()->getServer()->redstoneEnabled; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool|void */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->getLevel()->setBlock($this, $this, true); if($this->isActivated()){ $this->activate(); } } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ $this->getLevel()->setBlock($this, new Air(), true); if($this->isActivated()){ $this->deactivate(); } } /** * @param Block $block */ public function activateBlockWithoutWire(Block $block){ if(($block instanceof Door) or ($block instanceof Trapdoor) or ($block instanceof FenceGate)){ if(!$block->isOpened()) $block->onActivate(new Item(0)); } if($block->getId() == Block::TNT) $block->onActivate(new Item(Item::FLINT_AND_STEEL)); /** @var InactiveRedstoneLamp $block */ if($block->getId() == Block::REDSTONE_LAMP) $block->turnOn(); /** @var Dropper|Dispenser $block */ if($block->getId() == Block::DROPPER or $block->getId() == Block::DISPENSER) $block->activate(); /** @var PoweredRepeater $block */ if($block->getId() == Block::UNPOWERED_REPEATER_BLOCK){ if($this->equals($block->getSide($block->getDirection()))) $block->activate(); } } /** * @param Block $block */ public function activateBlock(Block $block){ $this->activateBlockWithoutWire($block); if($block->getId() == Block::REDSTONE_WIRE){ /** @var RedstoneWire $wire */ $wire = $block; $wire->calcSignal($this->maxStrength, RedstoneWire::ON); } } /** * @param Block $block */ public function deactivateBlock(Block $block){ $this->deactivateBlockWithoutWire($block); if($block->getId() == Block::REDSTONE_WIRE){ /** @var RedstoneWire $wire */ $wire = $block; $wire->calcSignal(0, RedstoneWire::OFF); } } /** * @param Block $block */ public function deactivateBlockWithoutWire(Block $block){ /** @var Door $block */ if(!$this->checkPower($block)){ if(($block instanceof Door) or ($block instanceof Trapdoor) or ($block instanceof FenceGate)){ if($block->isOpened()) $block->onActivate(new Item(0)); } /** @var ActiveRedstoneLamp $block */ if($block->getId() == Block::LIT_REDSTONE_LAMP) $block->turnOff(); } /** @var PoweredRepeater $block */ if($block->getId() == Block::POWERED_REPEATER_BLOCK){ if($this->equals($block->getSide($block->getDirection()))) $block->deactivate(); } } /** * @param array $ignore * * @return bool|void */ public function activate(array $ignore = []){ if($this->canCalc()){ $this->activated = true; /** @var Door $block */ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_DOWN]; foreach($sides as $side){ if(!in_array($side, $ignore)){ $block = $this->getSide($side); $this->activateBlock($block); } } } } /** * @param array $ignore * * @return bool|void */ public function deactivate(array $ignore = []){ if($this->canCalc()){ $this->activated = false; /** @var Door $block */ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH]; foreach($sides as $side){ if(!in_array($side, $ignore)){ $block = $this->getSide($side); $this->deactivateBlock($block); } } if(!in_array(Vector3::SIDE_DOWN, $ignore)){ $block = $this->getSide(Vector3::SIDE_DOWN); if(!$this->checkPower($block)){ /** @var $block ActiveRedstoneLamp */ if($block->getId() == Block::LIT_REDSTONE_LAMP) $block->turnOff(); } $block = $this->getSide(Vector3::SIDE_DOWN, 2); $this->deactivateBlock($block); } } } /** * @param Block $block * @param array $ignore * @param bool $ignoreWire * * @return bool */ public function checkPower(Block $block, array $ignore = [], $ignoreWire = false){ if($block instanceof PoweredRepeater){ if($block->getSide($block->getDirection())->isActivated($block)){ return true; } return false; } $sides = [ Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH ]; foreach($sides as $side){ if(!in_array($side, $ignore)){ $pos = $block->getSide($side); if($pos instanceof RedstoneSource){ if($pos->isActivated($this)){ if(($ignoreWire and $pos->getId() != self::REDSTONE_WIRE) or (!$ignoreWire and $pos->getId() != self::REDSTONE_WIRE)) return true; if(!$ignoreWire and $pos->getId() == self::REDSTONE_WIRE){ /** @var RedstoneWire $pos */ $cb = $pos->getUnconnectedSide(); if(!$cb[0]) return false; if($this->equals($pos->getSide($cb[0]))) return true; } } } } } if($block->getId() == Block::LIT_REDSTONE_LAMP and !in_array(Vector3::SIDE_UP, $ignore)){ $pos = $block->getSide(Vector3::SIDE_UP); if($pos instanceof RedstoneSource and $pos->getId() != self::REDSTONE_TORCH){ if($pos->isActivated($this)) return true; } } return false; } /** * @param Block $pos * @param array $ignore */ public function checkTorchOn(Block $pos, array $ignore = []){ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP]; foreach($sides as $side){ if(!in_array($side, $ignore)){ /** @var RedstoneTorch $block */ $block = $pos->getSide($side); if($block->getId() == self::REDSTONE_TORCH){ $faces = [ 1 => 4, 2 => 5, 3 => 2, 4 => 3, 5 => 0, 6 => 0, 0 => 0, ]; if($block->getSide($faces[$block->meta])->equals($pos)){ $ignoreBlock = $this->getSide(static::getOppositeSide($faces[$block->meta])); $block->turnOff(Level::blockHash($ignoreBlock->x, $ignoreBlock->y, $ignoreBlock->z)); } } } } } /** * @param Block $pos * @param array $ignore */ public function checkTorchOff(Block $pos, array $ignore = []){ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP]; foreach($sides as $side){ if(!in_array($side, $ignore)){ /** @var RedstoneTorch $block */ $block = $pos->getSide($side); if($block->getId() == self::UNLIT_REDSTONE_TORCH){ $faces = [ 1 => 4, 2 => 5, 3 => 2, 4 => 3, 5 => 0, 6 => 0, 0 => 0, ]; if($block->getSide($faces[$block->meta])->equals($pos)){ $ignoreBlock = $this->getSide(static::getOppositeSide($faces[$block->meta])); $block->turnOn(Level::blockHash($ignoreBlock->x, $ignoreBlock->y, $ignoreBlock->z)); } } } } } /** * @return int */ public function getStrength(){ if($this->isActivated()) return $this->maxStrength; return 0; } }meta = $meta; } /** * @return int */ public function getLightLevel(){ return 7; } /** * @return int */ public function getLastUpdateTime(){ return $this->getLevel()->getBlockTempData($this); } public function setLastUpdateTimeNow(){ $this->getLevel()->setBlockTempData($this, $this->getLevel()->getServer()->getTick()); } /** * @return bool|int */ public function canCalcTurn(){ if(!parent::canCalc()) return false; if($this->getLevel()->getServer()->getTick() != $this->getLastUpdateTime()) return true; return ($this->canScheduleUpdate() ? Level::BLOCK_UPDATE_SCHEDULED : false); } /** * @return bool */ public function canScheduleUpdate(){ return $this->getLevel()->getServer()->allowFrequencyPulse; } /** * @return int */ public function getFrequency(){ return $this->getLevel()->getServer()->pulseFrequency; } /** * @return string */ public function getName() : string{ return "Redstone Torch"; } /** * @param string $ignore * * @return bool */ public function turnOn($ignore = ""){ $result = $this->canCalcTurn(); $this->setLastUpdateTimeNow(); if($result === true){ $faces = [ 1 => 4, 2 => 5, 3 => 2, 4 => 3, 5 => 0, 6 => 0, 0 => 0, ]; $this->id = self::REDSTONE_TORCH; $this->getLevel()->setBlock($this, $this, true); $this->activateTorch([$faces[$this->meta]], [$ignore]); return true; }elseif($result === Level::BLOCK_UPDATE_SCHEDULED){ $this->ignore = $ignore; $this->getLevel()->scheduleUpdate($this, 20 * $this->getFrequency()); return true; } return false; } /** * @param string $ignore * * @return bool */ public function turnOff($ignore = ""){ $result = $this->canCalcTurn(); $this->setLastUpdateTimeNow(); if($result === true){ $faces = [ 1 => 4, 2 => 5, 3 => 2, 4 => 3, 5 => 0, 6 => 0, 0 => 0, ]; $this->id = self::UNLIT_REDSTONE_TORCH; $this->getLevel()->setBlock($this, $this, true); $this->deactivateTorch([$faces[$this->meta]], [$ignore]); return true; }elseif($result === Level::BLOCK_UPDATE_SCHEDULED){ $this->ignore = $ignore; $this->getLevel()->scheduleUpdate($this, 20 * $this->getFrequency()); return true; } return false; } /** * @param array $ignore * @param array $notCheck */ public function activateTorch(array $ignore = [], $notCheck = []){ if($this->canCalc()){ $this->activated = true; /** @var Door $block */ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP, Vector3::SIDE_DOWN]; foreach($sides as $side){ if(!in_array($side, $ignore)){ $block = $this->getSide($side); if(!in_array($hash = Level::blockHash($block->x, $block->y, $block->z), $notCheck)){ $this->activateBlock($block); } } } //$this->lastUpdateTime = $this->getLevel()->getServer()->getTick(); } } /** * @param array $ignore * * @return bool|void */ public function activate(array $ignore = []){ $this->activateTorch($ignore); } /** * @param array $ignore * * @return bool|void */ public function deactivate(array $ignore = []){ $this->deactivateTorch($ignore); } /** * @param array $ignore * @param array $notCheck */ public function deactivateTorch(array $ignore = [], array $notCheck = []){ if($this->canCalc()){ $this->activated = false; /** @var Door $block */ $sides = [Vector3::SIDE_EAST, Vector3::SIDE_WEST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH]; foreach($sides as $side){ if(!in_array($side, $ignore)){ $block = $this->getSide($side); if(!in_array($hash = Level::blockHash($block->x, $block->y, $block->z), $notCheck)){ $this->deactivateBlock($block); } } } if(!in_array(Vector3::SIDE_DOWN, $ignore)){ $block = $this->getSide(Vector3::SIDE_DOWN); if(!in_array($hash = Level::blockHash($block->x, $block->y, $block->z), $notCheck)){ if(!$this->checkPower($block)){ /** @var $block ActiveRedstoneLamp */ if($block->getId() == Block::LIT_REDSTONE_LAMP) $block->turnOff(); } $block = $this->getSide(Vector3::SIDE_DOWN, 2); $this->deactivateBlock($block); } } //$this->lastUpdateTime = $this->getLevel()->getServer()->getTick(); } } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ $faces = [ 1 => 4, 2 => 5, 3 => 2, 4 => 3, 5 => 0, 6 => 0, 0 => 0, ]; if($type === Level::BLOCK_UPDATE_NORMAL){ $below = $this->getSide(0); $side = $this->getDamage(); if($this->getSide($faces[$side])->isTransparent() === true and !($side === 0 and ($below->getId() === self::FENCE or $below->getId() === self::COBBLE_WALL )) ){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } $this->activate([$faces[$side]]); } if($type == Level::BLOCK_UPDATE_SCHEDULED){ if($this->id == self::UNLIT_REDSTONE_TORCH) $this->turnOn($this->ignore); else $this->turnOff($this->ignore); return Level::BLOCK_UPDATE_SCHEDULED; } return false; } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ $this->getLevel()->setBlock($this, new Air(), true, false); $faces = [ 1 => 4, 2 => 5, 3 => 2, 4 => 3, 5 => 0, 6 => 0, 0 => 0, ]; $this->deactivate([$faces[$this->meta]]); $this->getLevel()->setBlockTempData($this); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $below = $this->getSide(0); if($target->isTransparent() === false and $face !== 0){ $faces = [ 1 => 5, 2 => 4, 3 => 3, 4 => 2, 5 => 1, ]; $this->meta = $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); return true; }elseif( $below->isTransparent() === false or $below->getId() === self::FENCE or $below->getId() === self::COBBLE_WALL or $below->getId() == Block::REDSTONE_LAMP or $below->getId() == Block::LIT_REDSTONE_LAMP ){ $this->meta = 0; $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::LIT_REDSTONE_TORCH, 0, 1], ]; } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return true; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Redstone Wire"; } /** * @return int */ public function getStrength(){ return $this->meta; } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return ($this->meta > 0); } /** * @return int */ public function getHighestStrengthAround(){ $strength = 0; $hasChecked = [ Vector3::SIDE_WEST => false, Vector3::SIDE_EAST => false, Vector3::SIDE_NORTH => false, Vector3::SIDE_SOUTH => false ]; //check blocks around foreach($hasChecked as $side => $bool){ /** @var RedstoneSource $block */ $block = $this->getSide($side); if($block instanceof RedstoneSource){ if(($block->getStrength() > $strength) and $block->isActivated($this)) $strength = $block->getStrength(); $hasChecked[$side] = true; } } //check blocks above $baseBlock = $this->add(0, 1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $block = $this->getLevel()->getBlock($baseBlock->getSide($side)); if($block->getId() == $this->id){ if($block->getStrength() > $strength) $strength = $block->getStrength(); $hasChecked[$side] = true; } } } //check blocks below $baseBlock = $this->add(0, -1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $block = $this->getLevel()->getBlock($baseBlock->getSide($side)); if($block->getId() == $this->id){ if($block->getStrength() > $strength) $strength = $block->getStrength(); $hasChecked[$side] = true; } } } unset($block); unset($hasChecked); return $strength; } /** * @return array */ public function getConnectedWires(){ $hasChecked = [ Vector3::SIDE_WEST => false, Vector3::SIDE_EAST => false, Vector3::SIDE_NORTH => false, Vector3::SIDE_SOUTH => false ]; //check blocks around foreach($hasChecked as $side => $bool){ $block = $this->getSide($side); if($block instanceof RedstoneSource and !$block instanceof PoweredRepeater){ $hasChecked[$side] = true; } if($block instanceof PoweredRepeater){ if($this->equals($block->getSide($block->getOppositeDirection()))){ $hasChecked[$side] = true; } } } //check blocks above $baseBlock = $this->add(0, 1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $block = $this->getLevel()->getBlock($baseBlock->getSide($side)); if($block->getId() == $this->id){ $hasChecked[$side] = true; } } } //check blocks below $baseBlock = $this->add(0, -1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $block = $this->getLevel()->getBlock($baseBlock->getSide($side)); if($block->getId() == $this->id){ $hasChecked[$side] = true; } } } unset($block); return $hasChecked; } /** * @return array */ public function getUnconnectedSide(){ $connected = []; $notConnected = []; foreach($this->getConnectedWires() as $key => $bool){ if($bool){ $connected[] = $key; }else $notConnected[] = $key; } if(count($connected) == 1){ return [static::getOppositeSide($connected[0]), $connected]; }elseif(count($connected) == 3){ return [$notConnected[0], $connected]; }else return [false, $connected]; } /** * @param array $ignore * * @return bool|void */ public function activate(array $ignore = []){ if($this->canCalc()){ $block = $this->getSide(Vector3::SIDE_DOWN); /** @var ActiveRedstoneLamp $block */ if($block->getId() == Block::INACTIVE_REDSTONE_LAMP or $block->getId() == Block::INACTIVE_REDSTONE_LAMP) $block->turnOn(); $side = $this->getUnconnectedSide(); $sides = [Vector3::SIDE_WEST, Vector3::SIDE_EAST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH]; foreach($sides as $s){ if(!in_array($s, $side[1])){ $block = $this->getSide(Vector3::SIDE_DOWN)->getSide($s); $this->activateBlock($block); } } if($side[0] == false) return; $block = $this->getSide($side[0]); $this->activateBlock($block); if(!$block->isTransparent()){ $sides = [Vector3::SIDE_WEST, Vector3::SIDE_EAST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP, Vector3::SIDE_DOWN]; foreach($sides as $s){ if($s != static::getOppositeSide($side[0])){ $this->activateBlockWithoutWire($block->getSide($s)); } } } $this->checkTorchOn($block, [static::getOppositeSide($side)]); unset($connected, $notConnected); } } /** * @param array $ignore * * @return bool|void */ public function deactivate(array $ignore = []){ if($this->canCalc()){ $block = $this->getSide(Vector3::SIDE_DOWN); if($block->getId() == Block::ACTIVE_REDSTONE_LAMP){ /** @var ActiveRedstoneLamp $block */ if(!$this->checkPower($block, [Vector3::SIDE_UP], true)) $block->turnOff(); } $side = $this->getUnconnectedSide(); $sides = [Vector3::SIDE_WEST, Vector3::SIDE_EAST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH]; foreach($sides as $s){ if(!in_array($s, $side[1])){ $this->deactivateBlock($this->getSide(Vector3::SIDE_DOWN)->getSide($s)); } } if($side[0] == false) return; $block = $this->getSide($side[0]); $this->deactivateBlockWithoutWire($block); if(!$block->isTransparent()){ $sides = [Vector3::SIDE_WEST, Vector3::SIDE_EAST, Vector3::SIDE_SOUTH, Vector3::SIDE_NORTH, Vector3::SIDE_UP, Vector3::SIDE_DOWN]; foreach($sides as $s){ if($s != static::getOppositeSide($side[0])){ $this->deactivateBlockWithoutWire($block->getSide($s)); } } } $this->checkTorchOff($block, [static::getOppositeSide($side)]); unset($connected, $notConnected); } } /** * @param RedstoneWire $wire * @param array $powers * @param array $hasUpdated * @param bool $isStart * * @return array */ public function getPowerSources(RedstoneWire $wire, array $powers = [], array $hasUpdated = [], $isStart = false){ if(!$isStart){ $wire->meta = 0; $wire->getLevel()->setBlock($wire, $wire, true, false); $wire->deactivate(); } $hasChecked = [ Vector3::SIDE_WEST => false, Vector3::SIDE_EAST => false, Vector3::SIDE_NORTH => false, Vector3::SIDE_SOUTH => false ]; $hash = Level::blockHash($wire->x, $wire->y, $wire->z); if(!isset($hasUpdated[$hash])) $hasUpdated[$hash] = true; else return [$powers, $hasUpdated]; //check blocks around foreach($hasChecked as $side => $bool){ /** @var RedstoneWire $block */ $block = $wire->getSide($side); if($block instanceof RedstoneSource){ if($block->isActivated($wire)){ if($block->getId() != $this->id){ $powers[] = $block; }else{ $result = $this->getPowerSources($block, $powers, $hasUpdated); $powers = $result[0]; $hasUpdated = $result[1]; } $hasChecked[$side] = true; } } } //check blocks above $baseBlock = $wire->add(0, 1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $block = $this->getLevel()->getBlock($baseBlock->getSide($side)); if($block instanceof RedstoneSource){ if($block->isActivated($wire)){ if($block->getId() == $this->id){ $result = $this->getPowerSources($block, $powers, $hasUpdated); $powers = $result[0]; $hasUpdated = $result[1]; $hasChecked[$side] = true; } } } } } //check blocks below $baseBlock = $wire->add(0, -1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $block = $this->getLevel()->getBlock($baseBlock->getSide($side)); if($block instanceof RedstoneSource){ if($block->isActivated($wire)){ if($block->getId() == $this->id){ $result = $this->getPowerSources($block, $powers, $hasUpdated); $powers = $result[0]; $hasUpdated = $result[1]; $hasChecked[$side] = true; } } } } } return [$powers, $hasUpdated]; } /** * @param int $strength * @param int $type * @param array $hasUpdated * * @return array */ public function calcSignal($strength = 15, $type = self::ON, array $hasUpdated = []){ //This algorithm is provided by Stary and written by PeratX $hash = Level::blockHash($this->x, $this->y, $this->z); if(!in_array($hash, $hasUpdated)){ $hasUpdated[] = $hash; if($type == self::DESTROY or $type == self::OFF){ $this->meta = $strength; $this->getLevel()->setBlock($this, $this, true, false); if($type == self::DESTROY) $this->getLevel()->setBlock($this, new Air(), true, false); if($strength <= 0) $this->deactivate(); $powers = $this->getPowerSources($this, [], [], true); /** @var RedstoneSource $power */ foreach($powers[0] as $power){ $power->activate(); } }else{ if($strength <= 0) return $hasUpdated; if($type == self::PLACE) $strength = $this->getHighestStrengthAround() - 1; if($type == self::ON) $type = self::PLACE; if($this->getStrength() < $strength){ $this->meta = $strength; $this->getLevel()->setBlock($this, $this, true, false); $this->activate(); $hasChecked = [ Vector3::SIDE_WEST => false, Vector3::SIDE_EAST => false, Vector3::SIDE_NORTH => false, Vector3::SIDE_SOUTH => false ]; foreach($hasChecked as $side => $bool){ $needUpdate = $this->getSide($side); if(!in_array(Level::blockHash($needUpdate->x, $needUpdate->y, $needUpdate->z), $hasUpdated)){ $result = $this->updateNormalWire($needUpdate, $strength - 1, $type, $hasUpdated); if(count($result) != count($hasUpdated)){ $hasUpdated = $result; $hasChecked[$side] = true; } } } $baseBlock = $this->add(0, 1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $needUpdate = $this->getLevel()->getBlock($baseBlock->getSide($side)); if(!in_array(Level::blockHash($needUpdate->x, $needUpdate->y, $needUpdate->z), $hasUpdated)){ $result = $this->updateNormalWire($needUpdate, $strength - 1, $type, $hasUpdated); if(count($result) != count($hasUpdated)){ $hasUpdated = $result; $hasChecked[$side] = true; } } } } $baseBlock = $this->add(0, -1, 0); foreach($hasChecked as $side => $bool){ if(!$bool){ $needUpdate = $this->getLevel()->getBlock($baseBlock->getSide($side)); if(!in_array(Level::blockHash($needUpdate->x, $needUpdate->y, $needUpdate->z), $hasUpdated)){ $result = $this->updateNormalWire($needUpdate, $strength - 1, $type, $hasUpdated); if(count($result) != count($hasUpdated)){ $hasUpdated = $result; $hasChecked[$side] = true; } } } } } } } return $hasUpdated; } /** * @param Block $block * @param $strength * @param $type * @param array $hasUpdated * * @return array */ public function updateNormalWire(Block $block, $strength, $type, array $hasUpdated){ /** @var RedstoneWire $block */ if($block->getId() == Block::REDSTONE_WIRE){ if($block->getStrength() < $strength){ return $block->calcSignal($strength, $type, $hasUpdated); } } return $hasUpdated; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $down = $this->getSide(Vector3::SIDE_DOWN); if($down instanceof Transparent and $down->getId() != Block::REDSTONE_LAMP and $down->getId() != Block::LIT_REDSTONE_LAMP){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return true; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool|void */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(Vector3::SIDE_DOWN); if($down instanceof Transparent and $down->getId() != Block::REDSTONE_LAMP and $down->getId() != Block::LIT_REDSTONE_LAMP) return; else{ $this->getLevel()->setBlock($block, $this, true, false); $this->calcSignal(15, self::PLACE); } } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ if($this->canCalc()) $this->calcSignal(0, self::DESTROY); else $this->getLevel()->setBlock($this, new Air()); } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::REDSTONE, 0, 1] ]; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return string */ public function getName() : string{ if($this->meta === 0x01){ return "Red Sand"; } return "Sand"; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.8; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Sandstone", 1 => "Chiseled Sandstone", 2 => "Smooth Sandstone", 3 => "", ]; return $names[$this->meta & 0x03]; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [$this->id, $this->meta & 0x03, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.8; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ return "Sandstone Stairs"; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Oak Sapling", 1 => "Spruce Sapling", 2 => "Birch Sapling", 3 => "Jungle Sapling", 4 => "Acacia Sapling", 5 => "Dark Oak Sapling", 6 => "", 7 => "", ]; return $names[$this->meta & 0x07]; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === self::GRASS or $down->getId() === self::DIRT or $down->getId() === self::FARMLAND or $down->getId() === self::PODZOL){ $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal //TODO: change log type Tree::growTree($this->getLevel(), $this->x, $this->y, $this->z, new Random(mt_rand()), $this->meta & 0x07, false); if(($player->gamemode & 0x01) === 0){ $item->count--; } return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ //Growth if(mt_rand(1, 7) === 1){ if(($this->meta & 0x08) === 0x08){ Tree::growTree($this->getLevel(), $this->x, $this->y, $this->z, new Random(mt_rand()), $this->meta & 0x07, false); }else{ $this->meta |= 0x08; $this->getLevel()->setBlock($this, $this, true); return Level::BLOCK_UPDATE_RANDOM; } }else{ return Level::BLOCK_UPDATE_RANDOM; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, $this->meta & 0x07, 1], ]; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Sea Lantern"; } /** * @return float */ public function getHardness(){ return 0.3; } /** * @return int */ public function getLightLevel(){ return 15; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->hasEnchantment(Enchantment::TYPE_MINING_SILK_TOUCH)){ return [ [$this->id, 0, 1], ]; } return [ [Item::PRISMARINE_CRYSTALS, 0, 3], ]; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 1; } /** * @return bool */ public function isSolid(){ return false; } /** * @return string */ public function getName() : string{ return "Sign Post"; } /** * @return null */ public function getBoundingBox(){ return null; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($face !== 0){ $nbt = new CompoundTag("", [ "id" => new StringTag("id", Tile::SIGN), "x" => new IntTag("x", $block->x), "y" => new IntTag("y", $block->y), "z" => new IntTag("z", $block->z), "Text1" => new StringTag("Text1", ""), "Text2" => new StringTag("Text2", ""), "Text3" => new StringTag("Text3", ""), "Text4" => new StringTag("Text4", "") ]); if($player !== null){ $nbt->Creator = new StringTag("Creator", $player->getRawUniqueId()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } if($face === 1){ $this->meta = floor((($player->yaw + 180) * 16 / 360) + 0.5) & 0x0f; $this->getLevel()->setBlock($block, $this, true); }else{ $this->meta = $face; $this->getLevel()->setBlock($block, new WallSign($this->meta), true); } Tile::createTile(Tile::SIGN, $this->getLevel(), $nbt); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(Vector3::SIDE_DOWN)->getId() === Block::AIR){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::SIGN, 0, 1], ]; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Silver Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 1; } /** * @return bool */ public function getName() : bool{ return "Mob Head"; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ $x1 = 0; $x2 = 0; $z1 = 0; $z2 = 0; if($this->meta === 0 || $this->meta === 1){ return new AxisAlignedBB( $this->x + 0.25, $this->y, $this->z + 0.25, $this->x + 0.75, $this->y + 0.5, $this->z + 0.75 ); }elseif($this->meta === 2){ $x1 = 0.25; $x2 = 0.75; $z1 = 0; $z2 = 0.5; }elseif($this->meta === 3){ $x1 = 0.5; $x2 = 1; $z1 = 0.25; $z2 = 0.75; }elseif($this->meta === 4){ $x1 = 0.25; $x2 = 0.75; $z1 = 0.5; $z2 = 1; }elseif($this->meta === 5){ $x1 = 0; $x2 = 0.5; $z1 = 0.25; $z2 = 0.75; } return new AxisAlignedBB( $this->x + $x1, $this->y + 0.25, $this->z + $z1, $this->x + $x2, $this->y + 0.75, $this->z + $z2 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($face !== 0){ $this->meta = $face; if($face === 1){ $rot = floor(($player->yaw * 16 / 360) + 0.5) & 0x0F; }else{ $rot = 0; } $this->getLevel()->setBlock($block, $this, true); $moveMouth = false; if($item->getDamage() === SkullTile::TYPE_DRAGON){ if(in_array($target->getId(), [Block::REDSTONE_TORCH, Block::REDSTONE_BLOCK])) $moveMouth = true; //Temp-hacking Dragon Head Mouth Move } $nbt = new CompoundTag("", [ new StringTag("id", Tile::SKULL), new ByteTag("SkullType", $item->getDamage()), new ByteTag("Rot", $rot), new ByteTag("MouthMoving", (bool) $moveMouth), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } Tile::createTile("Skull", $this->getLevel(), $nbt); return true; } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $tile = $this->level->getTile($this); if($tile instanceof SkullTile){ return [ [Item::MOB_HEAD, $tile->getType(), 1] ]; } return []; } } meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Stone", 1 => "Sandstone", 2 => "Wooden", 3 => "Cobblestone", 4 => "Brick", 5 => "Stone Brick", 6 => "Quartz", 7 => "", ]; return (($this->meta & 0x08) > 0 ? "Upper " : "") . $names[$this->meta & 0x07] . " Slab"; } /** * @return int */ public function getBurnChance() : int{ $type = $this->meta & 0x07; if($type == self::WOODEN){ return 5; } return 0; } /** * @return int */ public function getBurnAbility() : int{ $type = $this->meta & 0x07; if($type == self::WOODEN){ return 5; } return 0; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ if(($this->meta & 0x08) > 0){ return new AxisAlignedBB( $this->x, $this->y + 0.5, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); }else{ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.5, $this->z + 1 ); } } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->meta &= 0x07; if($face === 0){ if($target->getId() === self::SLAB and ($target->getDamage() & 0x08) === 0x08 and ($target->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($target, Block::get(Item::DOUBLE_SLAB, $this->meta), true); return true; }elseif($block->getId() === self::SLAB and ($block->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_SLAB, $this->meta), true); return true; }else{ $this->meta |= 0x08; } }elseif($face === 1){ if($target->getId() === self::SLAB and ($target->getDamage() & 0x08) === 0 and ($target->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($target, Block::get(Item::DOUBLE_SLAB, $this->meta), true); return true; }elseif($block->getId() === self::SLAB and ($block->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_SLAB, $this->meta), true); return true; } //TODO: check for collision }else{ if($block->getId() === self::SLAB){ if(($block->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_SLAB, $this->meta), true); return true; } return false; }else{ if($fy > 0.5){ $this->meta |= 0x08; } } } if($block->getId() === self::SLAB and ($target->getDamage() & 0x07) !== ($this->meta & 0x07)){ return false; } $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [$this->id, $this->meta & 0x07, 1], ]; }else{ return []; } } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } }meta = $meta; } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @return int */ public function getHardness(){ return 0; } /** * @return string */ public function getName() : string{ return "Slime Block"; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 0.2; } public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return string */ public function getName() : string{ return "Snow Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isShovel() !== false){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0){ return [ [Item::SNOW_BLOCK, 0, 1], ]; }else{ return [ [Item::SNOWBALL, 0, 4], ]; } }else{ return []; } } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Snow Layer"; } /** * @return bool */ public function canBeReplaced(){ return true; } /** * @return float */ public function getHardness(){ return 0.1; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->isSolid()){ $this->getLevel()->setBlock($block, $this, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->getId() === self::AIR){ //Replace with common break method $this->getLevel()->setBlock($this, new Air(), true); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isShovel() !== false){ return [ [Item::SNOWBALL, 0, 1], ]; } return []; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Soul Sand"; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHOVEL; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 1 - 0.125, $this->z + 1 ); } }meta = $meta; } /** * @return float */ public function getHardness(){ return 0.6; } public function absorbWater(){ if(Server::getInstance()->absorbWater){ $range = $this->absorbRange / 2; for($xx = -$range; $xx <= $range; $xx++){ for($yy = -$range; $yy <= $range; $yy++){ for($zz = -$range; $zz <= $range; $zz++){ $block = $this->getLevel()->getBlock(new Vector3($this->x + $xx, $this->y + $yy, $this->z + $zz)); if($block->getId() === Block::WATER) $this->getLevel()->setBlock($block, Block::get(Block::AIR), true, true); if($block->getId() === Block::STILL_WATER) $this->getLevel()->setBlock($block, Block::get(Block::AIR), true, true); } } } } } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($this->meta == 0){ if($type === Level::BLOCK_UPDATE_NORMAL){ $blockAbove = $this->getSide(Vector3::SIDE_UP)->getId(); $blockBeneath = $this->getSide(Vector3::SIDE_DOWN)->getId(); $blockNorth = $this->getSide(Vector3::SIDE_NORTH)->getId(); $blockSouth = $this->getSide(Vector3::SIDE_SOUTH)->getId(); $blockEast = $this->getSide(Vector3::SIDE_EAST)->getId(); $blockWest = $this->getSide(Vector3::SIDE_WEST)->getId(); if($blockAbove === Block::WATER || $blockBeneath === Block::WATER || $blockNorth === Block::WATER || $blockSouth === Block::WATER || $blockEast === Block::WATER || $blockWest === Block::WATER ){ $this->absorbWater(); $this->getLevel()->setBlock($this, Block::get(Block::SPONGE, 1), true, true); return Level::BLOCK_UPDATE_NORMAL; } if($blockAbove === Block::STILL_WATER || $blockBeneath === Block::STILL_WATER || $blockNorth === Block::STILL_WATER || $blockSouth === Block::STILL_WATER || $blockEast === Block::STILL_WATER || $blockWest === Block::STILL_WATER ){ $this->absorbWater(); $this->getLevel()->setBlock($this, Block::get(Block::SPONGE, 1), true, true); return Level::BLOCK_UPDATE_NORMAL; } } return false; } return true; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Sponge", 1 => "Wet Sponge", ]; return $names[$this->meta & 0x0f]; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, $this->meta & 0x0f, 1], ]; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Spruce Door Block"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::SPRUCE_DOOR, 0, 1], ]; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.25; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "White Stained Clay", 1 => "Orange Stained Clay", 2 => "Magenta Stained Clay", 3 => "Light Blue Stained Clay", 4 => "Yellow Stained Clay", 5 => "Lime Stained Clay", 6 => "Pink Stained Clay", 7 => "Gray Stained Clay", 8 => "Light Gray Stained Clay", 9 => "Cyan Stained Clay", 10 => "Purple Stained Clay", 11 => "Blue Stained Clay", 12 => "Brown Stained Clay", 13 => "Green Stained Clay", 14 => "Red Stained Clay", 15 => "Black Stained Clay", ]; return $names[$this->meta & 0x0f]; } }getDamage(); $j = $damage & 0x03; $f = 0; $f1 = 0.5; $f2 = 0.5; $f3 = 1; if(($damage & 0x04) > 0){ $f = 0.5; $f1 = 1; $f2 = 0; $f3 = 0.5; } if($bb->intersectsWith($bb2 = AxisAlignedBB::getBoundingBoxFromPool( $this->x, $this->y + $f, $this->z, $this->x + 1, $this->y + $f1, $this->z + 1 ))){ $list[] = $bb2; } if($j === 0){ if($bb->intersectsWith($bb2 = AxisAlignedBB::getBoundingBoxFromPool( $this->x + 0.5, $this->y + $f2, $this->z, $this->x + 1, $this->y + $f3, $this->z + 1 ))){ $list[] = $bb2; } }elseif($j === 1){ if($bb->intersectsWith($bb2 = AxisAlignedBB::getBoundingBoxFromPool( $this->x, $this->y + $f2, $this->z, $this->x + 0.5, $this->y + $f3, $this->z + 1 ))){ $list[] = $bb2; } }elseif($j === 2){ if($bb->intersectsWith($bb2 = AxisAlignedBB::getBoundingBoxFromPool( $this->x, $this->y + $f2, $this->z + 0.5, $this->x + 1, $this->y + $f3, $this->z + 1 ))){ $list[] = $bb2; } }elseif($j === 3){ if($bb->intersectsWith($bb2 = AxisAlignedBB::getBoundingBoxFromPool( $this->x, $this->y + $f2, $this->z, $this->x + 1, $this->y + $f3, $this->z + 0.5 ))){ $list[] = $bb2; } } } */ /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ if(($this->getDamage() & 0x04) > 0){ return new AxisAlignedBB( $this->x, $this->y + 0.5, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); }else{ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.5, $this->z + 1 ); } } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 0, 1 => 2, 2 => 1, 3 => 3, ]; $this->meta = $faces[$player->getDirection()] & 0x03; if(($fy > 0.5 and $face !== 1) or $face === 0){ $this->meta |= 0x04; //Upside-down stairs } $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @return int */ public function getHardness(){ return 2; } /** * @return int */ public function getResistance(){ return 15; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= Tool::TIER_WOODEN){ return [ [$this->getId(), 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ static $names = [ self::NORMAL => "Stone", self::GRANITE => "Granite", self::POLISHED_GRANITE => "Polished Granite", self::DIORITE => "Diorite", self::POLISHED_DIORITE => "Polished Diorite", self::ANDESITE => "Andesite", self::POLISHED_ANDESITE => "Polished Andesite", 7 => "Unknown Stone", ]; return $names[$this->meta & 0x07]; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= Tool::TIER_WOODEN){ if($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) > 0 and $this->getDamage() === 0){ return [ [Item::STONE, 0, 1], ]; } return [ [$this->getDamage() === 0 ? Item::COBBLESTONE : Item::STONE, $this->getDamage(), 1], ]; }else{ return []; } } }meta = $meta; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return float */ public function getHardness(){ return 1.5; } /** * @return string */ public function getName() : string{ return "Stone Brick Stairs"; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.5; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Stone Bricks", 1 => "Mossy Stone Bricks", 2 => "Cracked Stone Bricks", 3 => "Chiseled Stone Bricks", ]; return $names[$this->meta & 0x03]; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::STONE_BRICKS, $this->meta & 0x03, 1], ]; }else{ return []; } } }isActivated()){ $this->meta ^= 0x08; $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->addSound(new ButtonClickSound($this)); $this->activate(); $this->getLevel()->scheduleUpdate($this, 40); } return true; } } meta = $meta; } /** * @return bool */ public function isSolid(){ return false; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @return int */ public function getHardness(){ return 2; } /** * @return string */ public function getName() : string{ if($this->meta === 0x01){ return "Mossy Cobblestone Wall"; } return "Cobblestone Wall"; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ $north = $this->canConnect($this->getSide(Vector3::SIDE_NORTH)); $south = $this->canConnect($this->getSide(Vector3::SIDE_SOUTH)); $west = $this->canConnect($this->getSide(Vector3::SIDE_WEST)); $east = $this->canConnect($this->getSide(Vector3::SIDE_EAST)); $n = $north ? 0 : 0.25; $s = $south ? 1 : 0.75; $w = $west ? 0 : 0.25; $e = $east ? 1 : 0.75; if($north and $south and !$west and !$east){ $w = 0.3125; $e = 0.6875; }elseif(!$north and !$south and $west and $east){ $n = 0.3125; $s = 0.6875; } return new AxisAlignedBB( $this->x + $w, $this->y, $this->z + $n, $this->x + $e, $this->y + 1.5, $this->z + $s ); } /** * @param Block $block * * @return bool */ public function canConnect(Block $block){ return ($block->getId() !== self::COBBLE_WALL and $block->getId() !== self::FENCE_GATE) ? $block->isSolid() and !$block->isTransparent() : true; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Stonecutter"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isPickaxe() >= 1){ return [ [Item::STONECUTTER, 0, 1], ]; }else{ return []; } } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Sugarcane"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::SUGARCANE, 0, 1], ]; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($item->getId() === Item::DYE and $item->getDamage() === 0x0F){ //Bonemeal if($this->getSide(0)->getId() !== self::SUGARCANE_BLOCK){ for($y = 1; $y < 3; ++$y){ $b = $this->getLevel()->getBlock(new Vector3($this->x, $this->y + $y, $this->z)); if($b->getId() === self::AIR){ Server::getInstance()->getPluginManager()->callEvent($ev = new BlockGrowEvent($b, new Sugarcane())); if(!$ev->isCancelled()){ $this->getLevel()->setBlock($b, $ev->getNewState(), true); } break; } } $this->meta = 0; $this->getLevel()->setBlock($this, $this, true); } if(($player->gamemode & 0x01) === 0){ $item->count--; } return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $down = $this->getSide(0); if($down->isTransparent() === true and $down->getId() !== self::SUGARCANE_BLOCK){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } }elseif($type === Level::BLOCK_UPDATE_RANDOM){ if($this->getSide(0)->getId() !== self::SUGARCANE_BLOCK){ if($this->meta === 0x0F){ for($y = 1; $y < 3; ++$y){ $b = $this->getLevel()->getBlock(new Vector3($this->x, $this->y + $y, $this->z)); if($b->getId() === self::AIR){ $this->getLevel()->setBlock($b, new Sugarcane(), true); break; } } $this->meta = 0; $this->getLevel()->setBlock($this, $this, true); }else{ ++$this->meta; $this->getLevel()->setBlock($this, $this, true); } return Level::BLOCK_UPDATE_RANDOM; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === self::SUGARCANE_BLOCK){ $this->getLevel()->setBlock($block, new Sugarcane(), true); return true; }elseif($down->getId() === self::GRASS or $down->getId() === self::DIRT or $down->getId() === self::SAND){ $block0 = $down->getSide(2); $block1 = $down->getSide(3); $block2 = $down->getSide(4); $block3 = $down->getSide(5); if(($block0 instanceof Water) or ($block1 instanceof Water) or ($block2 instanceof Water) or ($block3 instanceof Water)){ $this->getLevel()->setBlock($block, new Sugarcane(), true); return true; } } return false; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "TNT"; } /** * @return int */ public function getHardness(){ return 0; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getBurnChance() : int{ return 15; } /** * @return int */ public function getBurnAbility() : int{ return 100; } /** * @param Player|null $player */ public function prime(Player $player = null){ $this->meta = 1; if($player != null and $player->isCreative()){ $dropItem = false; }else{ $dropItem = true; } $mot = (new Random())->nextSignedFloat() * M_PI * 2; $tnt = Entity::createEntity("PrimedTNT", $this->getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x + 0.5), new DoubleTag("", $this->y), new DoubleTag("", $this->z + 0.5) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", -sin($mot) * 0.02), new DoubleTag("", 0.2), new DoubleTag("", -cos($mot) * 0.02) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), "Fuse" => new ByteTag("Fuse", 80) ]), $dropItem); $tnt->spawnToAll(); $this->level->addSound(new TNTPrimeSound($this)); } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type == Level::BLOCK_UPDATE_SCHEDULED){ $sides = [0, 1, 2, 3, 4, 5]; foreach($sides as $side){ $block = $this->getSide($side); if($block instanceof RedstoneSource and $block->isActivated($this)){ $this->prime(); $this->getLevel()->setBlock($this, new Air(), true); break; } } return Level::BLOCK_UPDATE_SCHEDULED; } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool|void */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->scheduleUpdate($this, 40); } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($item->getId() === Item::FLINT_STEEL){ $this->prime($player); $this->getLevel()->setBlock($this, new Air(), true); $item->useOn($this); return true; } return false; } } meta = $meta; } /** * @return bool */ public function canBeReplaced(){ return true; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Dead Shrub", 1 => "Tall Grass", 2 => "Fern", 3 => "" ]; return $names[$this->meta & 0x03]; } /** * @return int */ public function getBurnChance() : int{ return 60; } /** * @return int */ public function getBurnAbility() : int{ return 100; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $down = $this->getSide(0); if($down->getId() === self::GRASS){ $this->getLevel()->setBlock($block, $this, true); return true; } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if($this->getSide(0)->isTransparent() === true){ //Replace with common break method $this->getLevel()->setBlock($this, new Air(), false, false); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHEARS; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if(mt_rand(0, 15) === 0){ return [ [Item::WHEAT_SEEDS, 0, 1] ]; } return []; } } canConnect($this->getSide(2)); $flag1 = $this->canConnect($this->getSide(3)); $flag2 = $this->canConnect($this->getSide(4)); $flag3 = $this->canConnect($this->getSide(5)); if((!$flag2 or !$flag3) and ($flag2 or $flag3 or $flag or $flag1)){ if($flag2 and !$flag3){ $f = 0; }elseif(!$flag2 and $flag3){ $f1 = 1; } }else{ $f = 0; $f1 = 1; } if((!$flag or !$flag1) and ($flag2 or $flag3 or $flag or $flag1)){ if($flag and !$flag1){ $f2 = 0; }elseif(!$flag and $flag1){ $f3 = 1; } }else{ $f2 = 0; $f3 = 1; } return new AxisAlignedBB( $this->x + $f, $this->y, $this->z + $f2, $this->x + $f1, $this->y + 1, $this->z + $f3 ); } /** * @param Block $block * * @return bool */ public function canConnect(Block $block){ return $block->isSolid() or $block->getId() === $this->getId() or $block->getId() === self::GLASS_PANE or $block->getId() === self::GLASS; } }meta = $meta; } /** * @return int */ public function getLightLevel(){ return 15; } /** * @return string */ public function getName() : string{ return "Torch"; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ $below = $this->getSide(0); $side = $this->getDamage(); $faces = [ 1 => 4, 2 => 5, 3 => 2, 4 => 3, 5 => 0, 6 => 0, 0 => 0, ]; if($this->getSide($faces[$side])->isTransparent() === true and !($side === 0 and ($below->getId() === self::FENCE or $below->getId() === self::COBBLE_WALL or $below->getId() == Block::REDSTONE_LAMP or $below->getId() == Block::LIT_REDSTONE_LAMP) ) ){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $below = $this->getSide(0); if($target->isTransparent() === false and $face !== 0){ $faces = [ 1 => 5, 2 => 4, 3 => 3, 4 => 2, 5 => 1, ]; $this->meta = $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); return true; }elseif( $below->isTransparent() === false or $below->getId() === self::FENCE or $below->getId() === self::COBBLE_WALL or $below->getId() == Block::REDSTONE_LAMP or $below->getId() == Block::LIT_REDSTONE_LAMP ){ $this->meta = 0; $this->getLevel()->setBlock($block, $this, true, true); return true; } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Wooden Trapdoor"; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getResistance(){ return 15; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ $damage = $this->getDamage(); $f = 0.1875; if(($damage & 0x08) > 0){ $bb = new AxisAlignedBB( $this->x, $this->y + 1 - $f, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); }else{ $bb = new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + $f, $this->z + 1 ); } if(($damage & 0x04) > 0){ if(($damage & 0x03) === 0){ $bb->setBounds( $this->x, $this->y, $this->z + 1 - $f, $this->x + 1, $this->y + 1, $this->z + 1 ); }elseif(($damage & 0x03) === 1){ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + $f ); } if(($damage & 0x03) === 2){ $bb->setBounds( $this->x + 1 - $f, $this->y, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); } if(($damage & 0x03) === 3){ $bb->setBounds( $this->x, $this->y, $this->z, $this->x + $f, $this->y + 1, $this->z + 1 ); } } return $bb; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $directions = [ 0 => 1, 1 => 3, 2 => 0, 3 => 2 ]; if($player !== null){ $this->meta = $directions[$player->getDirection() & 0x03]; } if(($fy > 0.5 and $face !== self::SIDE_UP) or $face === self::SIDE_DOWN){ $this->meta |= 0b00000100; //top half of block } $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } /** * @return bool */ public function isOpened(){ return (($this->meta & 0b00001000) === 0); } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = \null){ $this->meta ^= 0b00001000; $this->getLevel()->setBlock($this, $this, true); $this->level->addSound(new DoorSound($this)); return true; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } } meta = $meta; } /** * @return AxisAlignedBB */ public function getBoundingBox(){ if($this->boundingBox === null){ $this->boundingBox = $this->recalculateBoundingBox(); } return $this->boundingBox; } /** * @return bool */ public function isSolid(){ return true; } /** * @return bool */ public function canBeFlowedInto(){ return false; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 2.5; } /** * @return float|int */ public function getResistance(){ return $this->getHardness() * 5; } /** * @return string */ public function getName() : string{ return "Trapped Chest"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x + 0.0625, $this->y, $this->z + 0.0625, $this->x + 0.9375, $this->y + 0.9475, $this->z + 0.9375 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $chest = null; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; for($side = 2; $side <= 5; ++$side){ if(($this->meta === 4 or $this->meta === 5) and ($side === 4 or $side === 5)){ continue; }elseif(($this->meta === 3 or $this->meta === 2) and ($side === 2 or $side === 3)){ continue; } $c = $this->getSide($side); if($c instanceof Chest and $c->getDamage() === $this->meta){ $tile = $this->getLevel()->getTile($c); if($tile instanceof TileChest and !$tile->isPaired()){ $chest = $tile; break; } } } $this->getLevel()->setBlock($block, $this, true, true); $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::CHEST), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); if($item->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $item->getCustomName()); } if($item->hasCustomBlockData()){ foreach($item->getCustomBlockData() as $key => $v){ $nbt->{$key} = $v; } } $tile = Tile::createTile("Chest", $this->getLevel(), $nbt); if($chest instanceof TileChest and $tile instanceof TileChest){ $chest->pairWith($tile); $tile->pairWith($chest); } return true; } /** * @param Item $item * * @return bool */ public function onBreak(Item $item){ $t = $this->getLevel()->getTile($this); if($t instanceof TileChest){ $t->unpair(); } $this->getLevel()->setBlock($this, new Air(), true, true); return true; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ $top = $this->getSide(1); if($top->isTransparent() !== true){ return true; } $t = $this->getLevel()->getTile($this); $chest = null; if($t instanceof TileChest){ $chest = $t; }else{ $nbt = new CompoundTag("", [ new ListTag("Items", []), new StringTag("id", Tile::CHEST), new IntTag("x", $this->x), new IntTag("y", $this->y), new IntTag("z", $this->z) ]); $nbt->Items->setTagType(NBT::TAG_Compound); $chest = Tile::createTile("Chest", $this->getLevel(), $nbt); } if(isset($chest->namedtag->Lock) and $chest->namedtag->Lock instanceof StringTag){ if($chest->namedtag->Lock->getValue() !== $item->getCustomName()){ return true; } } if($player->isCreative() and $player->getServer()->limitedCreative){ return true; } $player->addWindow($chest->getInventory()); } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Tripwire"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHEARS; } /** * @return int */ public function getHardness(){ return 0; } /** * @return int */ public function getResistance(){ return 0; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Tripwire Hook"; } /** * @return int */ public function getHardness(){ return 0; } /** * @return int */ public function getResistance(){ return 0; } } getLevel()->setBlock($this, new Air(), true); } }meta = $meta; } /** * @return bool */ public function isSolid(){ return false; } /** * @return string */ public function getName() : string{ return "Vines"; } /** * @return float */ public function getHardness(){ return 0.2; } /** * @return bool */ public function canPassThrough(){ return true; } /** * @return bool */ public function hasEntityCollision(){ return true; } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ $entity->resetFallDistance(); } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ $f1 = 1; $f2 = 1; $f3 = 1; $f4 = 0; $f5 = 0; $f6 = 0; $flag = $this->meta > 0; if(($this->meta & 0x02) > 0){ $f4 = max($f4, 0.0625); $f1 = 0; $f2 = 0; $f5 = 1; $f3 = 0; $f6 = 1; $flag = true; } if(($this->meta & 0x08) > 0){ $f1 = min($f1, 0.9375); $f4 = 1; $f2 = 0; $f5 = 1; $f3 = 0; $f6 = 1; $flag = true; } if(($this->meta & 0x01) > 0){ $f3 = min($f3, 0.9375); $f6 = 1; $f1 = 0; $f4 = 1; $f2 = 0; $f5 = 1; $flag = true; } if(!$flag and $this->getSide(1)->isSolid()){ $f2 = min($f2, 0.9375); $f5 = 1; $f1 = 0; $f4 = 1; $f3 = 0; $f6 = 1; } return new AxisAlignedBB( $this->x + $f1, $this->y + $f2, $this->z + $f3, $this->x + $f4, $this->y + $f5, $this->z + $f6 ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if(!$target->isTransparent() and $target->isSolid()){ $faces = [ 0 => 0, 1 => 0, 2 => 1, 3 => 4, 4 => 8, 5 => 2, ]; if(isset($faces[$face])){ $this->meta = $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); return true; } } return false; } /** * @param int $type * * @return bool */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ /*if($this->getSide(0)->getId() === self::AIR){ //Replace with common break method Server::getInstance()->api->entity->drop($this, Item::get(LADDER, 0, 1)); $this->getLevel()->setBlock($this, new Air(), true, true, true); return Level::BLOCK_UPDATE_NORMAL; }*/ } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ if($item->isShears()){ return [ [$this->id, 0, 1], ]; }else{ return []; } } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHEARS; } } 3, 3 => 2, 4 => 5, 5 => 4, ]; if($type === Level::BLOCK_UPDATE_NORMAL){ if(isset($faces[$this->meta])){ if($this->getSide($faces[$this->meta])->getId() === self::AIR){ $this->getLevel()->useBreakOn($this); } return Level::BLOCK_UPDATE_NORMAL; } } return false; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Water"; } /** * @param Entity $entity */ public function onEntityCollide(Entity $entity){ $entity->resetFallDistance(); if($entity->fireTicks > 0){ $entity->extinguish(); } $entity->resetFallDistance(); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $ret = $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->scheduleUpdate($this, $this->tickRate()); return $ret; } } meta = $meta; } /** * @return bool */ public function isSolid(){ return false; } /** * @return string */ public function getName() : string{ return "Lily Pad"; } /** * @return int */ public function getHardness(){ return 0; } /** * @return int */ public function getResistance(){ return 0; } /** * @return bool */ public function canPassThrough(){ return true; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x, $this->y + 0.0625, $this->z ); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($target instanceof Water){ $up = $target->getSide(Vector3::SIDE_UP); if($up->getId() === Block::AIR){ $this->getLevel()->setBlock($up, $this, true, true); return true; } } return false; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type === Level::BLOCK_UPDATE_NORMAL){ if(!($this->getSide(0) instanceof Water)){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1] ]; } }meta = $meta; } /** * @return int */ public function getResistance(){ return 3; } /** * @return float */ public function getHardness(){ return 0.6; } /** * @return string */ public function getName(){ return "Wet Sponge"; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Wheat Block"; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ $drops = []; if($this->meta >= 0x07){ $fortunel = $item->getEnchantmentLevel(Enchantment::TYPE_MINING_FORTUNE); $fortunel = $fortunel > 3 ? 3 : $fortunel; $drops[] = [Item::WHEAT, 0, 1]; $drops[] = [Item::WHEAT_SEEDS, 0, mt_rand(0, 3 + $fortunel)]; }else{ $drops[] = [Item::WHEAT_SEEDS, 0, 1]; } return $drops; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "White Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return string */ public function getName() : string{ static $names = [ self::OAK => "Oak Wood", self::SPRUCE => "Spruce Wood", self::BIRCH => "Birch Wood", self::JUNGLE => "Jungle Wood", ]; return $names[$this->meta & 0x03]; } /** * @return int */ public function getBurnChance() : int{ return 5; } /** * @return int */ public function getBurnAbility() : int{ return 10; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 0, 1 => 0, 2 => 0b1000, 3 => 0b1000, 4 => 0b0100, 5 => 0b0100, ]; $this->meta = ($this->meta & 0x03) | $faces[$face]; $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, $this->meta & 0x03, 1], ]; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } } "Acacia Wood", 1 => "Dark Oak Wood", 2 => "Unknown", 3 => "Unknown" ]; return $names[$this->meta & 0x03]; } } meta = $meta; } /** * @return string */ public function getName() : string{ return "Wood Door Block"; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return int */ public function getHardness(){ return 3; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [Item::WOODEN_DOOR, 0, 1], ]; } }meta = $meta; } /** * @return int */ public function getHardness(){ return 2; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "Oak", 1 => "Spruce", 2 => "Birch", 3 => "Jungle", 4 => "Acacia", 5 => "Dark Oak", 6 => "", 7 => "" ]; return (($this->meta & 0x08) === 0x08 ? "Upper " : "") . $names[$this->meta & 0x07] . " Wooden Slab"; } /** * @return AxisAlignedBB */ protected function recalculateBoundingBox(){ if(($this->meta & 0x08) > 0){ return new AxisAlignedBB( $this->x, $this->y + 0.5, $this->z, $this->x + 1, $this->y + 1, $this->z + 1 ); }else{ return new AxisAlignedBB( $this->x, $this->y, $this->z, $this->x + 1, $this->y + 0.5, $this->z + 1 ); } } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $this->meta &= 0x07; if($face === 0){ if($target->getId() === self::WOOD_SLAB and ($target->getDamage() & 0x08) === 0x08 and ($target->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($target, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true); return true; }elseif($block->getId() === self::WOOD_SLAB and ($block->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true); return true; }else{ $this->meta |= 0x08; } }elseif($face === 1){ if($target->getId() === self::WOOD_SLAB and ($target->getDamage() & 0x08) === 0 and ($target->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($target, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true); return true; }elseif($block->getId() === self::WOOD_SLAB and ($block->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true); return true; } }else{ //TODO: collision if($block->getId() === self::WOOD_SLAB){ if(($block->getDamage() & 0x07) === ($this->meta & 0x07)){ $this->getLevel()->setBlock($block, Block::get(Item::DOUBLE_WOOD_SLAB, $this->meta), true); return true; } return false; }else{ if($fy > 0.5){ $this->meta |= 0x08; } } } if($block->getId() === self::WOOD_SLAB and ($target->getDamage() & 0x07) !== ($this->meta & 0x07)){ return false; } $this->getLevel()->setBlock($block, $this, true, true); return true; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, $this->meta & 0x07, 1], ]; } }meta = $meta; } /** * @return string */ public function getName() : string{ return "Wood Stairs"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } /** * @return int */ public function getBurnChance() : int{ return 5; } /** * @return int */ public function getBurnAbility() : int{ return 20; } /** * @return int */ public function getHardness(){ return 2; } }meta = $meta; } /** * @param int $type * * @return bool|int */ public function onUpdate($type){ if($type == Level::BLOCK_UPDATE_SCHEDULED){ if($this->isActivated()){ $this->meta ^= 0x08; $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->addSound(new ButtonClickSound($this)); $this->deactivate(); } return Level::BLOCK_UPDATE_SCHEDULED; } if($type === Level::BLOCK_UPDATE_NORMAL){ $side = $this->getDamage(); if($this->isActivated()) $side ^= 0x08; $faces = [ 0 => 1, 1 => 0, 2 => 3, 3 => 2, 4 => 5, 5 => 4, ]; if($this->getSide($faces[$side]) instanceof Transparent){ $this->getLevel()->useBreakOn($this); return Level::BLOCK_UPDATE_NORMAL; } } return false; } /** * @param array $ignore * * @return bool|void */ public function deactivate(array $ignore = []){ parent::deactivate($ignore = []); $faces = [ 0 => 1, 1 => 0, 2 => 3, 3 => 2, 4 => 5, 5 => 4, ]; $side = $this->meta; if($this->isActivated()) $side ^= 0x08; $block = $this->getSide($faces[$side])->getSide(Vector3::SIDE_UP); if(!$this->equals($block)){ $this->deactivateBlock($block); } if($side != 1){ $this->deactivateBlock($this->getSide($faces[$side], 2)); } $this->checkTorchOff($this->getSide($faces[$side]), [static::getOppositeSide($faces[$side])]); } /** * @param array $ignore * * @return bool|void */ public function activate(array $ignore = []){ parent::activate($ignore = []); $faces = [ 0 => 1, 1 => 0, 2 => 3, 3 => 2, 4 => 5, 5 => 4, ]; $side = $this->meta; if($this->isActivated()) $side ^= 0x08; $block = $this->getSide($faces[$side])->getSide(Vector3::SIDE_UP); if(!$this->equals($block)){ $this->activateBlock($block); } if($side != 1){ $block = $this->getSide($faces[$side], 2); $this->activateBlock($block); } $this->checkTorchOn($this->getSide($faces[$side]), [static::getOppositeSide($faces[$side])]); } /** * @return string */ public function getName() : string{ return "Wooden Button"; } /** * @return float */ public function getHardness(){ return 0.5; } /** * @param Item $item * * @return mixed|void */ public function onBreak(Item $item){ if($this->isActivated()){ $this->meta ^= 0x08; $this->getLevel()->setBlock($this, $this, true, false); $this->deactivate(); } $this->getLevel()->setBlock($this, new Air(), true, false); } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ if($target->isTransparent() === false){ $this->meta = $face; $this->getLevel()->setBlock($block, $this, true, false); return true; } return false; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @param Block|null $from * * @return bool */ public function isActivated(Block $from = null){ return (($this->meta & 0x08) === 0x08); } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if(!$this->isActivated()){ $this->meta ^= 0x08; $this->getLevel()->setBlock($this, $this, true, false); $this->getLevel()->addSound(new ButtonClickSound($this)); $this->activate(); $this->getLevel()->scheduleUpdate($this, 30); } return true; } } meta = $meta; } /** * @return float */ public function getHardness(){ return 0.8; } /** * @return int */ public function getToolType(){ return Tool::TYPE_SHEARS; } /** * @return string */ public function getName() : string{ static $names = [ 0 => "White Wool", 1 => "Orange Wool", 2 => "Magenta Wool", 3 => "Light Blue Wool", 4 => "Yellow Wool", 5 => "Lime Wool", 6 => "Pink Wool", 7 => "Gray Wool", 8 => "Light Gray Wool", 9 => "Cyan Wool", 10 => "Purple Wool", 11 => "Blue Wool", 12 => "Brown Wool", 13 => "Green Wool", 14 => "Red Wool", 15 => "Black Wool", ]; return $names[$this->meta & 0x0f]; } /** * @return int */ public function getBurnChance() : int{ return 30; } /** * @return int */ public function getBurnAbility() : int{ return 60; } }meta = $meta; } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @return float */ public function getHardness(){ return 2.5; } /** * @return string */ public function getName() : string{ return "Crafting Table"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_AXE; } /** * @param Item $item * @param Player|null $player * * @return bool */ public function onActivate(Item $item, Player $player = null){ if($player instanceof Player){ if($player->getServer()->limitedCreative and $player->isCreative()) return true; $player->craftingType = Player::CRAFTING_BIG; } return true; } /** * @param Item $item * * @return array */ public function getDrops(Item $item) : array{ return [ [$this->id, 0, 1], ]; } }meta = $meta; } /** * @return float */ public function getHardness(){ return 1.4; } /** * @return string */ public function getName(){ return "Yellow Glazed Terracotta"; } /** * @return int */ public function getToolType(){ return Tool::TYPE_PICKAXE; } /** * @param Item $item * @param Block $block * @param Block $target * @param int $face * @param float $fx * @param float $fy * @param float $fz * @param Player|null $player * * @return bool */ public function place(Item $item, Block $block, Block $target, $face, $fx, $fy, $fz, Player $player = null){ $faces = [ 0 => 4, 1 => 2, 2 => 5, 3 => 3, ]; $this->meta = $faces[$player instanceof Player ? $player->getDirection() : 0]; $this->getLevel()->setBlock($block, $this, true, true); return true; } }commandData = self::generateDefaultData(); $this->name = $this->nextLabel = $this->label = $name; $this->setDescription($description); $this->usageMessage = $usageMessage === null ? "/" . $name : $usageMessage; $this->setAliases($aliases); $this->timings = new TimingsHandler("** Command: " . $name); } /** * Returns an \stdClass containing command data * * @return \stdClass */ public function getDefaultCommandData() : \stdClass{ return $this->commandData; } /** * Generates modified command data for the specified player * for AvailableCommandsPacket. * * @param Player $player * * @return \stdClass|null */ public function generateCustomCommandData(Player $player){ //TODO: fix command permission filtering on join /*if(!$this->testPermissionSilent($player)){ return null; }*/ $customData = clone $this->commandData; $customData->aliases = $this->getAliases(); /*foreach($customData->overloads as &$overload){ if(isset($overload->pocketminePermission) and !$player->hasPermission($overload->pocketminePermission)){ unset($overload); } }*/ return $customData; } /** * @return \stdClass */ public function getOverloads() : \stdClass{ return $this->commandData->overloads; } /** * @param CommandSender $sender * @param string $commandLabel * @param string[] $args * * @return mixed */ public abstract function execute(CommandSender $sender, $commandLabel, array $args); /** * @return string */ public function getName() : string{ return $this->name; } /** * @return string */ public function getPermission(){ return $this->commandData->pocketminePermission ?? null; } /** * @param string|null $permission */ public function setPermission($permission){ if($permission !== null){ $this->commandData->pocketminePermission = $permission; }else{ unset($this->commandData->pocketminePermission); } } /** * @param CommandSender $target * * @return bool */ public function testPermission(CommandSender $target){ if($this->testPermissionSilent($target)){ return true; } if($this->permissionMessage === null){ $target->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); }elseif($this->permissionMessage !== ""){ $target->sendMessage(str_replace("", $this->getPermission(), $this->permissionMessage)); } return false; } /** * @param CommandSender $target * * @return bool */ public function testPermissionSilent(CommandSender $target){ if(($perm = $this->getPermission()) === null or $perm === ""){ return true; } foreach(explode(";", $perm) as $permission){ if($target->hasPermission($permission)){ return true; } } return false; } /** * @return string */ public function getLabel(){ return $this->label; } /** * @param $name * * @return bool */ public function setLabel($name){ $this->nextLabel = $name; if(!$this->isRegistered()){ $this->timings = new TimingsHandler("** Command: " . $name); $this->label = $name; return true; } return false; } /** * Registers the command into a Command map * * @param CommandMap $commandMap * * @return bool */ public function register(CommandMap $commandMap){ if($this->allowChangesFrom($commandMap)){ $this->commandMap = $commandMap; return true; } return false; } /** * @param CommandMap $commandMap * * @return bool */ public function unregister(CommandMap $commandMap){ if($this->allowChangesFrom($commandMap)){ $this->commandMap = null; $this->activeAliases = $this->commandData->aliases; $this->label = $this->nextLabel; return true; } return false; } /** * @param CommandMap $commandMap * * @return bool */ private function allowChangesFrom(CommandMap $commandMap){ return $this->commandMap === null or $this->commandMap === $commandMap; } /** * @return bool */ public function isRegistered(){ return $this->commandMap !== null; } /** * @return string[] */ public function getAliases(){ return $this->activeAliases; } /** * @return string */ public function getPermissionMessage(){ return $this->permissionMessage; } /** * @return string */ public function getDescription(){ return $this->commandData->description; } /** * @return string */ public function getUsage(){ return $this->usageMessage; } /** * @param string[] $aliases */ public function setAliases(array $aliases){ $this->commandData->aliases = $aliases; if(!$this->isRegistered()){ $this->activeAliases = (array) $aliases; } } /** * @param string $description */ public function setDescription($description){ $this->commandData->description = $description; } /** * @param string $permissionMessage */ public function setPermissionMessage($permissionMessage){ $this->permissionMessage = $permissionMessage; } /** * @param string $usage */ public function setUsage($usage){ $this->usageMessage = $usage; } /** * @return \stdClass */ public static final function generateDefaultData() : \stdClass{ if(self::$defaultDataTemplate === null){ self::$defaultDataTemplate = json_decode(file_get_contents(Server::getInstance()->getFilePath() . "src/pocketmine/resources/command_default.json")); } return clone self::$defaultDataTemplate; } /** * @param CommandSender $source * @param string $message * @param bool $sendToSource */ public static function broadcastCommandMessage(CommandSender $source, $message, $sendToSource = true){ if($message instanceof TextContainer){ $m = clone $message; $result = "[" . $source->getName() . ": " . ($source->getServer()->getLanguage()->get($m->getText()) !== $m->getText() ? "%" : "") . $m->getText() . "]"; $users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE); $colored = TextFormat::GRAY . TextFormat::ITALIC . $result; $m->setText($result); $result = clone $m; $m->setText($colored); $colored = clone $m; }else{ $users = $source->getServer()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_ADMINISTRATIVE); $result = new TranslationContainer("chat.type.admin", [$source->getName(), $message]); $colored = new TranslationContainer(TextFormat::GRAY . TextFormat::ITALIC . "%chat.type.admin", [$source->getName(), $message]); } if($sendToSource === true and !($source instanceof ConsoleCommandSender)){ $source->sendMessage($message); } foreach($users as $user){ if($user instanceof CommandSender){ if($user instanceof ConsoleCommandSender){ $user->sendMessage($result); }elseif($user !== $source){ $user->sendMessage($colored); } } } } /** * @return string */ public function __toString(){ return $this->name; } } buffer = new \Threaded; $opts = getopt("", ["disable-readline"]); if((extension_loaded("readline") and !isset($opts["disable-readline"]) and !$this->isPipe(STDIN))){ $this->type = self::TYPE_READLINE; } $this->start(); } public function shutdown(){ $this->shutdown = true; } public function quit(){ $wait = microtime(true) + 0.5; while(microtime(true) < $wait){ if($this->isRunning()){ usleep(100000); }else{ parent::quit(); return; } } $message = "Thread blocked for unknown reason"; if($this->type === self::TYPE_PIPED){ $message = "STDIN is being piped from another location and the pipe is blocked, cannot stop safely"; } throw new \ThreadException($message); } private function initStdin(){ global $stdin; if(is_resource($stdin)){ fclose($stdin); } $stdin = fopen("php://stdin", "r"); if($this->isPipe($stdin)){ $this->type = self::TYPE_PIPED; }else{ $this->type = self::TYPE_STREAM; } } /** * Checks if the specified stream is a FIFO pipe. * * @param resource $stream * * @return bool */ private function isPipe($stream) : bool{ return is_resource($stream) and ((function_exists("posix_isatty") and !posix_isatty($stream)) or ((fstat($stream)["mode"] & 0170000) === 0010000)); } /** * Reads a line from the console and adds it to the buffer. This method may block the thread. * * @return bool if the main execution should continue reading lines */ private function readLine() : bool{ $line = ""; if($this->type === self::TYPE_READLINE){ $line = trim(readline("> ")); if($line !== ""){ readline_add_history($line); }else{ return true; } }else{ global $stdin; if(!is_resource($stdin)){ $this->initStdin(); } switch($this->type){ case self::TYPE_STREAM: $r = [$stdin]; if(($count = stream_select($r, $w, $e, 0, 200000)) === 0){ //nothing changed in 200000 microseconds return true; }elseif($count === false){ //stream error $this->initStdin(); } if(($raw = fgets($stdin)) !== false){ $line = trim($raw); }else{ return false; //user pressed ctrl+c? } break; case self::TYPE_PIPED: if(($raw = fgets($stdin)) === false){ //broken pipe or EOF $this->initStdin(); $this->synchronized(function(){ $this->wait(200000); }); //prevent CPU waste if it's end of pipe return true; //loop back round }else{ $line = trim($raw); } break; } } if($line !== ""){ $this->buffer[] = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", $line); } return true; } /** * Reads a line from console, if available. Returns null if not available * * @return string|null */ public function getLine(){ if($this->buffer->count() !== 0){ return $this->buffer->shift(); } return null; } public function run(){ if($this->type !== self::TYPE_READLINE){ $this->initStdin(); } while(!$this->shutdown and $this->readLine()) ; if($this->type !== self::TYPE_READLINE){ global $stdin; fclose($stdin); } } /** * @return string */ public function getThreadName(){ return "Console"; } } perm = new PermissibleBase($this); } /** * @param \pocketmine\permission\Permission|string $name * * @return bool */ public function isPermissionSet($name){ return $this->perm->isPermissionSet($name); } /** * @param \pocketmine\permission\Permission|string $name * * @return bool */ public function hasPermission($name){ return $this->perm->hasPermission($name); } /** * @param Plugin $plugin * @param string $name * @param bool $value * * @return \pocketmine\permission\PermissionAttachment */ public function addAttachment(Plugin $plugin, $name = null, $value = null){ return $this->perm->addAttachment($plugin, $name, $value); } /** * @param PermissionAttachment $attachment * * @return void */ public function removeAttachment(PermissionAttachment $attachment){ $this->perm->removeAttachment($attachment); } public function recalculatePermissions(){ $this->perm->recalculatePermissions(); } /** * @return \pocketmine\permission\PermissionAttachmentInfo[] */ public function getEffectivePermissions(){ return $this->perm->getEffectivePermissions(); } /** * @return bool */ public function isPlayer(){ return false; } /** * @return \pocketmine\Server */ public function getServer(){ return Server::getInstance(); } /** * @param string $message */ public function sendMessage($message){ if($message instanceof TextContainer){ $message = $this->getServer()->getLanguage()->translate($message); }else{ $message = $this->getServer()->getLanguage()->translateString($message); } foreach(explode("\n", trim($message)) as $line){ MainLogger::getLogger()->info($line); } } /** * @return string */ public function getName() : string{ return "CONSOLE"; } /** * @return bool */ public function isOp(){ return true; } /** * @param bool $value */ public function setOp($value){ } }formatStrings = $formatStrings; } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ $commands = []; $result = false; foreach($this->formatStrings as $formatString){ try{ $commands[] = $this->buildCommand($formatString, $args); }catch(\Throwable $e){ if($e instanceof \InvalidArgumentException){ $sender->sendMessage(TextFormat::RED . $e->getMessage()); }else{ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.exception")); $logger = $sender->getServer()->getLogger(); if($logger instanceof MainLogger){ $logger->logException($e); } } return false; } } foreach($commands as $command){ $result |= Server::getInstance()->dispatchCommand($sender, $command); } return (bool) $result; } /** * @param string $formatString * @param array $args * * @return string * @throws \InvalidArgumentException */ private function buildCommand($formatString, array $args){ $index = strpos($formatString, '$'); while($index !== false){ $start = $index; if($index > 0 and $formatString{$start - 1} === "\\"){ $formatString = substr($formatString, 0, $start - 1) . substr($formatString, $start); $index = strpos($formatString, '$', $index); continue; } $required = false; if($formatString{$index + 1} == '$'){ $required = true; ++$index; } ++$index; $argStart = $index; while($index < strlen($formatString) and self::inRange(ord($formatString{$index}) - 48, 0, 9)){ ++$index; } if($argStart === $index){ throw new \InvalidArgumentException("Invalid replacement token"); } $position = intval(substr($formatString, $argStart, $index)); if($position === 0){ throw new \InvalidArgumentException("Invalid replacement token"); } --$position; $rest = false; if($index < strlen($formatString) and $formatString{$index} === "-"){ $rest = true; ++$index; } $end = $index; if($required and $position >= count($args)){ throw new \InvalidArgumentException("Missing required argument " . ($position + 1)); } $replacement = ""; if($rest and $position < count($args)){ for($i = $position; $i < count($args); ++$i){ if($i !== $position){ $replacement .= " "; } $replacement .= $args[$i]; } }elseif($position < count($args)){ $replacement .= $args[$position]; } $formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end); $index = $start + strlen($replacement); $index = strpos($formatString, '$', $index); } return $formatString; } /** * @param int $i * @param int $j * @param int $k * * @return bool */ private static function inRange($i, $j, $k){ return $i >= $j and $i <= $k; } }owningPlugin = $owner; $this->executor = $owner; $this->usageMessage = ""; } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->owningPlugin->isEnabled()){ return false; } if(!$this->testPermission($sender)){ return false; } $success = $this->executor->onCommand($sender, $this, $commandLabel, $args); if(!$success and $this->usageMessage !== ""){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); } return $success; } /** * @return CommandExecutor|Plugin */ public function getExecutor(){ return $this->executor; } /** * @param CommandExecutor $executor */ public function setExecutor(CommandExecutor $executor){ $this->executor = ($executor != null) ? $executor : $this->owningPlugin; } /** * @return Plugin */ public function getPlugin(){ return $this->owningPlugin; } } getServer()->getLanguage()->translate($message); }else{ $message = $this->getServer()->getLanguage()->translateString($message); } $this->messages .= trim($message, "\r\n") . "\n"; } /** * @return string */ public function getMessage(){ return $this->messages; } /** * @return string */ public function getName() : string{ return "Rcon"; } }server = $server; /** @var bool[] */ $this->commandConfig = $this->server->getProperty("commands"); $this->setDefaultCommands(); } private function setDefaultCommands(){ $this->register("pocketmine", new WeatherCommand("weather")); $this->register("pocketmine", new BanCidCommand("bancid")); $this->register("pocketmine", new PardonCidCommand("pardoncid")); $this->register("pocketmine", new BanCidByNameCommand("bancidbyname")); $this->register("pocketmine", new BanIpByNameCommand("banipbyname")); $this->register("pocketmine", new ExtractPharCommand("extractphar")); $this->register("pocketmine", new ExtractPluginCommand("ep")); $this->register("pocketmine", new MakePluginCommand("mp")); $this->register("pocketmine", new MakeServerCommand("ms")); $this->register("pocketmine", new LoadPluginCommand("loadplugin")); $this->register("pocketmine", new LvdatCommand("lvdat")); $this->register("pocketmine", new BiomeCommand("biome")); $this->register("pocketmine", new CaveCommand("cave")); $this->register("pocketmine", new ChunkInfoCommand("chunkinfo")); $this->register("pocketmine", new VersionCommand("version")); $this->register("pocketmine", new FillCommand("fill")); $this->register("pocketmine", new PluginsCommand("plugins")); $this->register("pocketmine", new SeedCommand("seed")); $this->register("pocketmine", new HelpCommand("help"), null, true); $this->register("pocketmine", new StopCommand("stop"), null, true); $this->register("pocketmine", new TellCommand("tell")); $this->register("pocketmine", new DefaultGamemodeCommand("defaultgamemode")); $this->register("pocketmine", new BanCommand("ban")); $this->register("pocketmine", new BanIpCommand("ban-ip")); $this->register("pocketmine", new BanListCommand("banlist")); $this->register("pocketmine", new PardonCommand("pardon")); $this->register("pocketmine", new PardonIpCommand("pardon-ip")); $this->register("pocketmine", new SayCommand("say")); $this->register("pocketmine", new MeCommand("me")); $this->register("pocketmine", new ListCommand("list")); $this->register("pocketmine", new DifficultyCommand("difficulty")); $this->register("pocketmine", new KickCommand("kick")); $this->register("pocketmine", new OpCommand("op")); $this->register("pocketmine", new DeopCommand("deop")); $this->register("pocketmine", new WhitelistCommand("whitelist")); $this->register("pocketmine", new SaveOnCommand("save-on")); $this->register("pocketmine", new SaveOffCommand("save-off")); $this->register("pocketmine", new SaveCommand("save-all"), null, true); $this->register("pocketmine", new GiveCommand("give")); $this->register("pocketmine", new EffectCommand("effect")); $this->register("pocketmine", new EnchantCommand("enchant")); $this->register("pocketmine", new ParticleCommand("particle")); $this->register("pocketmine", new GamemodeCommand("gamemode")); $this->register("pocketmine", new KillCommand("kill")); $this->register("pocketmine", new SpawnpointCommand("spawnpoint")); $this->register("pocketmine", new SetWorldSpawnCommand("setworldspawn")); $this->register("pocketmine", new SummonCommand("summon")); $this->register("pocketmine", new TeleportCommand("tp")); $this->register("pocketmine", new TransferServerCommand("transfer")); $this->register("pocketmine", new TimeCommand("time")); $this->register("pocketmine", new TimingsCommand("timings")); $this->register("pocketmine", new ReloadCommand("reload"), null, true); $this->register("pocketmine", new XpCommand("xp")); $this->register("pocketmine", new SetBlockCommand("setblock")); if($this->server->getProperty("debug.commands", false)){ $this->register("pocketmine", new StatusCommand("status"), null, true); $this->register("pocketmine", new GarbageCollectorCommand("gc"), null, true); $this->register("pocketmine", new DumpMemoryCommand("dumpmemory"), null, true); } } /** * @param string $fallbackPrefix * @param array $commands */ public function registerAll($fallbackPrefix, array $commands){ foreach($commands as $command){ $this->register($fallbackPrefix, $command); } } /** * @param string $fallbackPrefix * @param Command $command * @param null $label * @param bool $overrideConfig * * @return bool */ public function register($fallbackPrefix, Command $command, $label = null, $overrideConfig = false){ if($label === null){ $label = $command->getName(); } $label = strtolower(trim($label)); //Check if command was disabled in config and for override if(!(($this->commandConfig[$label] ?? $this->commandConfig["default"] ?? true) or $overrideConfig)){ return false; } $fallbackPrefix = strtolower(trim($fallbackPrefix)); $registered = $this->registerAlias($command, false, $fallbackPrefix, $label); $aliases = $command->getAliases(); foreach($aliases as $index => $alias){ if(!$this->registerAlias($command, true, $fallbackPrefix, $alias)){ unset($aliases[$index]); } } $command->setAliases($aliases); if(!$registered){ $command->setLabel($fallbackPrefix . ":" . $label); } $command->register($this); return $registered; } /** * @param Command $command * @param $isAlias * @param $fallbackPrefix * @param $label * * @return bool */ private function registerAlias(Command $command, $isAlias, $fallbackPrefix, $label){ $this->knownCommands[$fallbackPrefix . ":" . $label] = $command; if(($command instanceof VanillaCommand or $isAlias) and isset($this->knownCommands[$label])){ return false; } if(isset($this->knownCommands[$label]) and $this->knownCommands[$label]->getLabel() !== null and $this->knownCommands[$label]->getLabel() === $label){ return false; } if(!$isAlias){ $command->setLabel($label); } $this->knownCommands[$label] = $command; return true; } /** * @param CommandSender $sender * @param Command $command * @param $label * @param array $args * @param int $offset */ private function dispatchAdvanced(CommandSender $sender, Command $command, $label, array $args, $offset = 0){ if(isset($args[$offset])){ $argsTemp = $args; switch($args[$offset]){ case "@a": $p = $this->server->getOnlinePlayers(); if(count($p) <= 0){ $sender->sendMessage(TextFormat::RED . "No players online"); //TODO: add language }else{ foreach($p as $player){ $argsTemp[$offset] = $player->getName(); $this->dispatchAdvanced($sender, $command, $label, $argsTemp, $offset + 1); } } break; case "@r": $players = $this->server->getOnlinePlayers(); if(count($players) > 0){ $argsTemp[$offset] = $players[array_rand($players)]->getName(); $this->dispatchAdvanced($sender, $command, $label, $argsTemp, $offset + 1); } break; case "@p": if($sender instanceof Player){ $argsTemp[$offset] = $sender->getName(); $this->dispatchAdvanced($sender, $command, $label, $argsTemp, $offset + 1); }else{ $sender->sendMessage(TextFormat::RED . "You must be a player!"); //TODO: add language } break; default: $this->dispatchAdvanced($sender, $command, $label, $argsTemp, $offset + 1); } }else $command->execute($sender, $label, $args); } /** * @param CommandSender $sender * @param string $commandLine * * @return bool */ public function dispatch(CommandSender $sender, $commandLine){ $args = explode(" ", $commandLine); if(count($args) === 0){ return false; } $sentCommandLabel = strtolower(array_shift($args)); $target = $this->getCommand($sentCommandLabel); if($target === null){ return false; } $target->timings->startTiming(); try{ if($this->server->advancedCommandSelector){ $this->dispatchAdvanced($sender, $target, $sentCommandLabel, $args); }else{ $target->execute($sender, $sentCommandLabel, $args); } }catch(\Throwable $e){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.exception")); $this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.command.exception", [$commandLine, (string) $target, $e->getMessage()])); $logger = $sender->getServer()->getLogger(); if($logger instanceof MainLogger){ $logger->logException($e); } } $target->timings->stopTiming(); return true; } public function clearCommands(){ foreach($this->knownCommands as $command){ $command->unregister($this); } $this->knownCommands = []; $this->setDefaultCommands(); } /** * @param string $name * * @return null|Command */ public function getCommand($name){ if(isset($this->knownCommands[$name])){ return $this->knownCommands[$name]; } return null; } /** * @return Command[] */ public function getCommands(){ return $this->knownCommands; } /** * @return void */ public function registerServerAliases(){ $values = $this->server->getCommandAliases(); foreach($values as $alias => $commandStrings){ if(strpos($alias, ":") !== false or strpos($alias, " ") !== false){ $this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.illegal", [$alias])); continue; } $targets = []; $bad = ""; foreach($commandStrings as $commandString){ $args = explode(" ", $commandString); $command = $this->getCommand($args[0]); if($command === null){ if(strlen($bad) > 0){ $bad .= ", "; } $bad .= $commandString; }else{ $targets[] = $commandString; } } if(strlen($bad) > 0){ $this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.command.alias.notFound", [$alias, $bad])); continue; } //These registered commands have absolute priority if(count($targets) > 0){ $this->knownCommands[strtolower($alias)] = new FormattedCommandAlias(strtolower($alias), $targets); }else{ unset($this->knownCommands[strtolower($alias)]); } } } }setPermission("pocketmine.command.bancidbyname"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $name = array_shift($args); $reason = implode(" ", $args); if($sender->getServer()->getPlayer($name) instanceof Player) $target = $sender->getServer()->getPlayer($name); else return false; $sender->getServer()->getCIDBans()->addBan($target->getClientId(), $reason, null, $sender->getName()); $target->kick($reason !== "" ? "Banned by admin. Reason:" . $reason : "Banned by admin."); Command::broadcastCommandMessage($sender, new TranslationContainer("%commands.bancidbyname.success", [$target !== null ? $target->getName() : $name])); return true; } } setPermission("pocketmine.command.bancid"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $cid = array_shift($args); $reason = implode(" ", $args); $sender->getServer()->getCIDBans()->addBan($cid, $reason, null, $sender->getName()); $player = null; foreach($sender->getServer()->getOnlinePlayers() as $p){ if($p->getClientId() == $cid){ $p->kick($reason !== "" ? "Banned by admin. Reason:" . $reason : "Banned by admin."); $player = $p; break; } } Command::broadcastCommandMessage($sender, new TranslationContainer("%commands.bancid.success", [$player !== null ? $player->getName() : $cid])); return true; } } setPermission("pocketmine.command.ban.player"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $name = array_shift($args); $reason = implode(" ", $args); $sender->getServer()->getNameBans()->addBan($name, $reason, null, $sender->getName()); if(($player = $sender->getServer()->getPlayerExact($name)) instanceof Player){ $player->kick($reason !== "" ? "Banned by admin. Reason: " . $reason : "Banned by admin."); } Command::broadcastCommandMessage($sender, new TranslationContainer("%commands.ban.success", [$player !== null ? $player->getName() : $name])); return true; } } setPermission("pocketmine.command.banipbyname"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return \true; } if(\count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return \false; } $name = \array_shift($args); $reason = \implode(" ", $args); if($sender->getServer()->getPlayer($name) instanceof Player) $target = $sender->getServer()->getPlayer($name); else return \false; $sender->getServer()->getIPBans()->addBan($target->getAddress(), $reason, \null, $sender->getName()); if(($player = $sender->getServer()->getPlayerExact($name)) instanceof Player){ $player->kick($reason !== "" ? "Banned by admin. Reason:" . $reason : "Banned by admin."); } Command::broadcastCommandMessage($sender, new TranslationContainer("%commands.banipbyname.success", [$player !== \null ? $player->getName() : $name])); return \true; } } setPermission("pocketmine.command.ban.ip"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $value = array_shift($args); $reason = implode(" ", $args); if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $value)){ $this->processIPBan($value, $sender, $reason); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.banip.success", [$value])); }else{ if(($player = $sender->getServer()->getPlayer($value)) instanceof Player){ $this->processIPBan($player->getAddress(), $sender, $reason); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.banip.success.players", [$player->getAddress(), $player->getName()])); }else{ $sender->sendMessage(new TranslationContainer("commands.banip.invalid")); return false; } } return true; } /** * @param $ip * @param CommandSender $sender * @param $reason */ private function processIPBan($ip, CommandSender $sender, $reason){ $sender->getServer()->getIPBans()->addBan($ip, $reason, null, $sender->getName()); foreach($sender->getServer()->getOnlinePlayers() as $player){ if($player->getAddress() === $ip){ $player->kick($reason !== "" ? $reason : "IP banned."); } } $sender->getServer()->getNetwork()->blockAddress($ip, -1); } }setPermission("pocketmine.command.ban.list"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $args[0] = (isset($args[0]) ? strtolower($args[0]) : ""); switch($args[0]){ case "ips": $list = $sender->getServer()->getIPBans(); $title = "commands.banlist.ips"; break; case "cids": $list = $list = $sender->getServer()->getCIDBans(); $title = "commands.banlist.cids"; break; case "players": $list = $sender->getServer()->getNameBans(); $title = "commands.banlist.players"; break; default: $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $message = ""; $list = $list->getEntries(); foreach($list as $entry){ $message .= $entry->getName() . ", "; } $sender->sendMessage(Server::getInstance()->getLanguage()->translateString($title, [count($list)])); $sender->sendMessage(\substr($message, 0, -2)); return true; } }" ); $this->setPermission("pocketmine.command.biome"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if($sender instanceof Player){ if($args[0] == "set"){ $biome = isset($args[1]) ? $args[1] : 1;//默认改成草原 if(isset($sender->selectedPos[0]) and isset($sender->selectedPos[1])){ if(is_numeric($biome) === false){ $sender->sendMessage(TextFormat::RED . new TranslationContainer("pocketmine.command.biome.wrongBio")); return false; } $biome = (int) $biome; if($sender->selectedLev[0] !== $sender->selectedLev[1]){ $sender->sendMessage(TextFormat::RED . new TranslationContainer("pocketmine.command.biome.wrongLev")); return false; } $x1 = min($sender->selectedPos[0][0], $sender->selectedPos[1][0]); $z1 = min($sender->selectedPos[0][1], $sender->selectedPos[1][1]); $x2 = max($sender->selectedPos[0][0], $sender->selectedPos[1][0]); $z2 = max($sender->selectedPos[0][1], $sender->selectedPos[1][1]); $level = $sender->selectedLev[0]; for($x = $x1; $x <= $x2; $x++){ for($z = $z1; $z <= $z2; $z++){ $level->setBiomeId($x, $z, $biome); } } $sender->sendMessage(new TranslationContainer("pocketmine.command.biome.set", [$biome])); }else{ $sender->sendMessage(new TranslationContainer("pocketmine.command.biome.noPos")); } }elseif($args[0] == "pos1"){ $x = floor($sender->getX()); $z = floor($sender->getZ()); $sender->selectedLev[0] = $sender->getLevel(); $sender->selectedPos[0][0] = $x; $sender->selectedPos[0][1] = $z; $sender->sendMessage(new TranslationContainer("pocketmine.command.biome.posset", [$sender->selectedLev[0]->getName(), $x, $z, "1"])); }elseif($args[0] == "pos2"){ $x = floor($sender->getX()); $z = floor($sender->getZ()); $sender->selectedLev[1] = $sender->getLevel(); $sender->selectedPos[1][0] = $x; $sender->selectedPos[1][1] = $z; $sender->sendMessage(new TranslationContainer("pocketmine.command.biome.posset", [$sender->selectedLev[1]->getName(), $x, $z, "2"])); }elseif($args[0] == "get"){ $x = floor($sender->getX()); $z = floor($sender->getZ()); $biome = $sender->getLevel()->getBiomeId($x, $z); $sender->sendMessage(new TranslationContainer("pocketmine.command.biome.get", [$biome])); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } }else{ $sender->sendMessage(new TranslationContainer("commands.generic.runingame")); return false; } return true; } } setPermission("pocketmine.command.cave"); } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->testPermission($sender)){ return true; } if(!$sender instanceof Player){ $sender->sendMessage(TextFormat::RED . "Please run this command in-game!"); return true; } if($args[0] == "getmypos"){ $sender->sendMessage("Your position: ({$sender->getX()}, {$sender->getY()}, {$sender->getZ()}, {$sender->getLevel()->getFolderName()})"); return true; } //0:旋转角度 1:洞穴长度 2:分叉数 3:洞穴强度 if(count($args) > 8){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } //是否自动获取玩家位置 $level = isset($args[7]) ? $sender->getServer()->getLevelByName($args[7]) : $sender->getLevel(); if(!$level instanceof Level){ $sender->sendMessage(TextFormat::RED . "Wrong LevelName"); return false; } $x = isset($args[4]) ? $args[4] : $sender->getX(); $y = isset($args[5]) ? $args[5] : $sender->getY(); $z = isset($args[6]) ? $args[6] : $sender->getZ(); $pos = new Position($x, $y, $z, $level); $caves[0] = isset($args[0]) ? $args[0] : mt_rand(1, 360); $caves[1] = isset($args[1]) ? $args[1] : mt_rand(10, 300); $caves[2] = isset($args[2]) ? $args[2] : mt_rand(1, 6); $caves[4] = isset($args[3]) ? $args[3] : mt_rand(1, 10); $caves[3] = [false, true, true]; $sender->sendMessage(new TranslationContainer("pocketmine.commands.cave.info", [$caves[0], $caves[1], $caves[2], $caves[4]])); $sender->sendMessage(new TranslationContainer(TextFormat::YELLOW . "%pocketmine.commands.cave.start")); $sender->sendMessage($pos->x . " " . $pos->y . " " . $pos->z); $this->caves($pos, $caves); $sender->sendMessage(new TranslationContainer(TextFormat::GREEN . "%pocketmine.commands.cave.success")); return true; } /** * @param $v1 * @param $v2 * * @return float|int */ public function chu($v1, $v2){ if($v2 == 0) return 0; return $v1 / $v2; } /** * @param $yaw * @param $pitch * * @return Vector3 */ public function getDirectionVector($yaw, $pitch){ $y = -\sin(\deg2rad($pitch)); $xz = \cos(\deg2rad($pitch)); $x = -$xz * \sin(\deg2rad($yaw)); $z = $xz * \cos(\deg2rad($yaw)); $temporalVector = new Vector3($x, $y, $z); return $temporalVector->normalize(); } /** * @param Position $pos * @param $cave * @param bool $tt */ public function caves(Position $pos, $cave, $tt = false){ $x = $pos->x; $y = $pos->y; $z = $pos->z; $level = $pos->getLevel(); $ls = $cave[1]; //长度 $cv = $cave[2]; //分叉数 $lofs = $ls / $cave[2]; $ls2 = $lofs; $yaw = $cave[0]; if($cave[0] >= 0 || $cave[0] < 0){ }else{ $yaw = mt_rand(0, 100) * 72; } $pitch = -45; //$pi = M_PI / 180; $s1 = [$x, $y, $z]; $s2 = [$x, $y, $z]; //$i = -10 + mt_rand(0, 100) * 0.2; //$i = mt_rand(8, 25) / 10; $i = 1; for($u = 0; $u <= $ls; $u += $i){ if($pitch > 12) $pitch = -45; $pitch += 5 + mt_rand(0, 5); $pos->getLevel()->getServer()->getLogger()->debug("[Caves] " . TextFormat::YELLOW . "yaw: $yaw pitch: $pitch"); if($tt) $pitch = mt_rand(0, 100) * 0.05; //$s2[0] = $s1[0] -\sin($yaw / 180 * M_PI) * \cos($pitch / 180 * M_PI) * $i; //$s2[1] = $s1[1] +\sin($pitch / 180 * M_PI) * $i; //$s2[2] = $s1[2] + \cos($yaw / 180 * M_PI) * \cos($pitch / 180 * M_PI) * $i; #echo "s1: "; //var_dump($s1); $see = $this->getDirectionVector($yaw, $pitch); $s2[0] = $s1[0] + $see->x * $i; $s2[1] = $s1[1] - $see->y * $i; $s2[2] = $s1[2] + $see->z * $i; //echo "s2: "; //var_dump($s2); if($s2[1] < 10){ $s2[1] = 10 + mt_rand(0, 10); } if($u > $lofs){ $cv--; if($cave[3][1] === false) $cv = 0; $lofs += $ls2; $newPos = new Position($s2[0], $s2[1], $s2[2], $level); $this->caves($newPos, [$yaw + 90 * (round(mt_rand(0, 100) / 100) * 2 - 1), $ls - $u, $cv, [false, $cave[3][1], $cave[3][2]], 0], $tt); } //$exPos = new Position($s2[0], $s2[1], $s2[2], $level); //$this->explodeBlocks($exPos, mt_rand(2, 4), mt_rand(1, 4)); if(mt_rand(0, 100) > 80){ $add = mt_rand(-10, 10); }else{ $add = mt_rand(-45, 45); } $yaw = $yaw + $add; $yaw = $yaw % 360; $yaw = $yaw >= 0 ? $yaw : 360 + $yaw; //$i = 5 + mt_rand(0, 100) * 0.05; $x = $s1[0]; $y = $s1[1]; $z = $s1[2]; $x2 = $s2[0]; $y2 = $s2[1]; $z2 = $s2[2]; $l = max(abs($x - $x2), abs($y - $y2), abs($z - $z2)); for($m = 0; $m <= $l; $m++){ //$v = $level->getBlock(new Vector3(round($this->chu($x + $m, $l * ($x2 - $x))), round($y + $this->chu($m, $l * ($y2 - $y))), round($z + $this->chu($m, $l * ($z2 - $z)))))->getId(); //if ($v != 0 and $v != 95) $liu = mt_rand(0, 200) == 100; $this->fdx(round($x + $this->chu($m, $l * ($x2 - $x))), round($y + $this->chu($m, $l * ($y2 - $y))), round($z + $this->chu($m, $l * ($z2 - $z))), $level, $liu); } $s1 = [$s2[0], $s2[1], $s2[2]]; } if(mt_rand(0, 10) >= 5 and $s2[1] <= 40){ $this->lavaSpawn($level, $s2[0], $s2[1], $s2[2]); } /* if ($cave[3][0]) { $l = $cave[4]; $x = $s2[0]; $y = $s2[1]; $z = $s2[2]; for ($i = -$l; $i <= $l; $i += 2) { for ($j = -$l; $j <= $l; $j += 2) { if ($i * $i + $j * $j <= pow($l - 0.3 * $l * mt_rand(0, 1000) / 1000, 2)) { if ($level->getBlock(new Vector3($x + $i, $y - 1, $z + $j))->getId() != 0) { $this->fdx($x + $i, $y - 1 + 2 * mt_rand(0, 1000) / 1000, $z + $j, $level); } } if ($i * $i + $j * $j <= pow($l - 0.5 * $l * mt_rand(0, 1000) / 1000, 2)) { if ($level->getBlock(new Vector3($x + $i, $y + 3, $z + $j))->getId() != 0) { $this->fdx($x + $i, $y + 3 + 2 * mt_rand(0, 1000) / 1000, $z + $j, $level); } } } } //if ($level->getBlock(new Vector3($s2[0], $s2[1] - 4, $s2[2]))->getId() != 0 && $cave[3][2] && mt_rand(0, 100) / 100 > 0.5) $this->tiankengy($level, $s2[0], $s2[1], $s2[2], $l * 0.6, 11, 0); } else if ($cave[3][2]) { $l = $cave[4]; if ($pitch < -10 && $pitch > -45 && $level->getBlock(new Vector3($s2[0], $s2[1] - 3, $s2[2]))->getId() != 0) $this->tiankengy($level, $s2[0], $s2[1], $s2[2], $l / 2, 11, 0); }*/ //echo "\n 矿洞生成完成\n"; } /** * @param Level $level * @param $x * @param $y * @param $z */ public function lavaSpawn(Level $level, $x, $y, $z){ $level->getServer()->getLogger()->info("生成岩浆中 " . "floor($x)" . ", " . "floor($y)" . ", " . floor($z)); for($xx = $x - 20; $xx <= $x + 20; $xx++){ for($zz = $z - 20; $zz <= $z + 20; $zz++){ for($yy = $y; $yy > $y - 4; $yy--){ $id = $level->getBlockIdAt($xx, $yy, $zz); if($id == 0){ $level->setBlockIdAt($xx, $yy, $zz, 10); $level->setBlockDataAt($xx, $yy, $zz, 0); } } } } $level->setBlock(new Vector3($x, $y, $z), new Lava()); } /** * @param Position $source * @param int $rays * @param int $size */ public function explodeBlocks(Position $source, $rays = 16, $size = 4){ $vector = new Vector3(0, 0, 0); $vBlock = new Vector3(0, 0, 0); $stepLen = 0.3; $mRays = \intval($rays - 1); $affectedBlocks = []; for($i = 0; $i < $rays; ++$i){ for($j = 0; $j < $rays; ++$j){ for($k = 0; $k < $rays; ++$k){ if($i === 0 or $i === $mRays or $j === 0 or $j === $mRays or $k === 0 or $k === $mRays){ $vector->setComponents($i / $mRays * 2 - 1, $j / $mRays * 2 - 1, $k / $mRays * 2 - 1); $vector->setComponents(($vector->x / ($len = $vector->length())) * $stepLen, ($vector->y / $len) * $stepLen, ($vector->z / $len) * $stepLen); $pointerX = $source->x; $pointerY = $source->y; $pointerZ = $source->z; for($blastForce = $size * (\mt_rand(700, 1300) / 1000); $blastForce > 0; $blastForce -= $stepLen * 0.75){ $x = (int) $pointerX; $y = (int) $pointerY; $z = (int) $pointerZ; $vBlock->x = $pointerX >= $x ? $x : $x - 1; $vBlock->y = $pointerY >= $y ? $y : $y - 1; $vBlock->z = $pointerZ >= $z ? $z : $z - 1; if($vBlock->y < 0 or $vBlock->y > 127){ break; } $block = $source->getLevel()->getBlock($vBlock); if($block->getId() !== 0){ $blastForce -= (mt_rand(1, 3) / 5 + 0.3) * $stepLen; if($blastForce > 0){ if(!isset($affectedBlocks[$index = (\PHP_INT_SIZE === 8 ? ((($block->x) & 0xFFFFFFF) << 35) | ((($block->y) & 0x7f) << 28) | (($block->z) & 0xFFFFFFF) : ($block->x) . ":" . ($block->y) . ":" . ($block->z))])){ $affectedBlocks[$index] = $block; } } } $pointerX += $vector->x; $pointerY += $vector->y; $pointerZ += $vector->z; } } } } } foreach($affectedBlocks as $block){ if($block instanceof Block){ $block->getLevel()->setBlock($block, new Air(), false, false); } } } /** * @param $x * @param $y * @param $z * @param Level $level * @param bool $liu */ public function fdx($x, $y, $z, Level $level, $liu = false){ //$this->getLogger()->info(TextFormat::GREEN."fdx!"); for($i = 1; $i < mt_rand(2, 4); $i++){ $level->setBlockIdAt($x + $i - 2, $y - 1, $z + 1, 0); $level->setBlockIdAt($x + $i - 2, $y - 1, $z, 0); $level->setBlockIdAt($x + $i - 2, $y - 1, $z - 1, 0); $level->setBlockIdAt($x + $i - 2, $y - 1, $z - 1, 0); $level->setBlockIdAt($x + $i - 2, $y - 1, $z + 1, 0); $level->setBlockIdAt($x + $i - 2, $y + 2, $z + 1, 0); $level->setBlockIdAt($x + $i - 2, $y + 2, $z, 0); $level->setBlockIdAt($x + $i - 2, $y + 2, $z - 1, 0); } for($i = 1; $i < mt_rand(3, 6); $i++){ $level->setBlockIdAt($x + $i - 3, $y + 1, $z + 2, 0); $level->setBlockIdAt($x + $i - 3, $y + 1, $z + 1, 0); $level->setBlockIdAt($x + $i - 3, $y + 1, $z, 0); $level->setBlockIdAt($x + $i - 3, $y + 1, $z - 1, 0); $level->setBlockIdAt($x + $i - 3, $y + 1, $z - 2, 0); $level->setBlockIdAt($x + $i - 3, $y, $z + 2, 0); $level->setBlockIdAt($x + $i - 3, $y, $z + 1, 0); $level->setBlockIdAt($x + $i - 3, $y, $z, 0); $level->setBlockIdAt($x + $i - 3, $y, $z - 1, 0); $level->setBlockIdAt($x + $i - 3, $y, $z - 2, 0); } if($liu){ $l = (mt_rand(0, 1) == 0) ? new Water() : new Lava(); $i = mt_rand(3, 6); $level->setBlock(new Vector3($x + $i - 3, $y + 1, $z + 3), $l); } } /** * @param $a * * @return array */ public function ranz($a){ $n = []; $j = 0; for($m = 0; $m < $a; $m++){ $n[] = mt_rand(0, 999) / 1000 - 1; } for($m = 0; $m < $a; $m++){ foreach($n as $q){ $min = min($n); if($n[$q] == $min){ $n[$q] = $j; $j++; break; } } } return $n; } /** * @param Level $level * @param $x * @param $y * @param $z * @param $l * @param $id * @param $bd */ public function tiankengy(Level $level, $x, $y, $z, $l, $id, $bd){ if($level->getBlock(new Vector3($x, $y, $z))->getId() == 0) $level->setBlock(new Vector3($x, $y, $z), Item::get($id, $bd)->getBlock()); if($l >= 0){ $random = mt_rand(0, 99999) / 100000; $mz = $this->ranz(4); foreach($mz as $sss){ switch($mz[$sss]){ case 0: if($level->getBlock(new Vector3($x, $y, $z - 1))->getId() == 0) $this->tiankengy($level, $x, $y, $z - 1, $l - $random, $id, $bd); break; case 1: if($level->getBlock(new Vector3($x, $y, $z + 1))->getId() == 0) $this->tiankengy($level, $x, $y, $z + 1, $l - $random, $id, $bd); break; case 2: if($level->getBlock(new Vector3($x + 1, $y, $z))->getId() == 0) $this->tiankengy($level, $x + 1, $y, $z, $l - $random, $id, $bd); break; case 3: if($level->getBlock(new Vector3($x - 1, $y, $z))->getId() == 0) $this->tiankengy($level, $x - 1, $y, $z, $l - $random, $id, $bd); break; } } } } }setPermission("pocketmine.command.chunkinfo"); } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->testPermission($sender)){ return true; } if(!$sender instanceof Player and count($args) < 4){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if($sender instanceof Player and count($args) < 4){ $pos = $sender->getPosition(); }else{ $level = $sender->getServer()->getLevelByName($args[3]); if(!$level instanceof Level){ $sender->sendMessage(TextFormat::RED . "Invalid level name"); return false; } $pos = new Position((int) $args[0], (int) $args[1], (int) $args[2], $level); } if(!isset($args[4]) or $args[0] != "regenerate"){ $chunk = $pos->getLevel()->getChunk($pos->x >> 4, $pos->z >> 4); McRegion::getRegionIndex($chunk->getX(), $chunk->getZ(), $x, $z); $sender->sendMessage("Region X: $x Region Z: $z"); }elseif($args[4] == "regenerate"){ foreach($sender->getServer()->getOnlinePlayers() as $p){ if($p->getLevel() === $pos->getLevel()){ $p->kick(TextFormat::AQUA . "A chunk of this chunk is regenerating, please re-login.", false); } } $pos->getLevel()->regenerateChunk($pos->x >> 4, $pos->z >> 4); } return true; } }setPermission("pocketmine.command.defaultgamemode"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $gameMode = Server::getGamemodeFromString($args[0]); if($gameMode !== -1){ $sender->getServer()->setConfigInt("gamemode", $gameMode); $sender->sendMessage(new TranslationContainer("commands.defaultgamemode.success", [Server::getGamemodeString($gameMode)])); }else{ $sender->sendMessage("You entered an unknown gamemode"); } return true; } } setPermission("pocketmine.command.op.take"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $name = array_shift($args); $player = $sender->getServer()->getOfflinePlayer($name); $player->setOp(false); if($player instanceof Player){ $player->sendMessage(TextFormat::GRAY . "You are no longer op!"); } Command::broadcastCommandMessage($sender, new TranslationContainer("commands.deop.success", [$player->getName()])); return true; } }setPermission("pocketmine.command.difficulty"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) !== 1){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $difficulty = Server::getDifficultyFromString($args[0]); if($sender->getServer()->isHardcore()){ $difficulty = 3; } if($difficulty !== -1){ $sender->getServer()->setConfigInt("difficulty", $difficulty); $pk = new SetDifficultyPacket(); $pk->difficulty = $sender->getServer()->getDifficulty(); $sender->getServer()->broadcastPacket($sender->getServer()->getOnlinePlayers(), $pk); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.difficulty.success", [$difficulty])); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } return true; } }setPermission("pocketmine.command.dumpmemory"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } Command::broadcastCommandMessage($sender, "Dumping server memory"); $sender->getServer()->getMemoryManager()->dumpServerMemory(isset($args[0]) ? $args[0] : $sender->getServer()->getDataPath() . "/memory_dumps/memoryDump_" . date("D_M_j-H.i.s-T_Y", time()), 48, 80); return true; } } setPermission("pocketmine.command.effect;pocketmine.command.effect.other"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } $player = $sender->getServer()->getPlayer($args[0]); if($player === null){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); return true; } if($player->getName() != $sender->getName() && !$sender->hasPermission("pocketmine.command.effect.other")){ $sender->sendMessage("You don't have permission to give effect to other player ."); return true; } if(strtolower($args[1]) === "clear"){ foreach($player->getEffects() as $effect){ $player->removeEffect($effect->getId()); } $sender->sendMessage(new TranslationContainer("commands.effect.success.removed.all", [$player->getDisplayName()])); return true; } $effect = Effect::getEffectByName($args[1]); if($effect === null){ $effect = Effect::getEffect((int) $args[1]); } if($effect === null){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.effect.notFound", [(string) $args[1]])); return true; } $duration = 300; $amplification = 0; if(count($args) >= 3){ $duration = (int) $args[2]; if(!($effect instanceof InstantEffect)){ $duration *= 20; } }elseif($effect instanceof InstantEffect){ $duration = 1; } if(count($args) >= 4){ $amplification = (int) $args[3]; } if(count($args) >= 5){ $v = strtolower($args[4]); if($v === "on" or $v === "true" or $v === "t" or $v === "1"){ $effect->setVisible(false); } } if($duration === 0){ if(!$player->hasEffect($effect->getId())){ if(count($player->getEffects()) === 0){ $sender->sendMessage(new TranslationContainer("commands.effect.failure.notActive.all", [$player->getDisplayName()])); }else{ $sender->sendMessage(new TranslationContainer("commands.effect.failure.notActive", [$effect->getName(), $player->getDisplayName()])); } return true; } if($player->removeEffect($effect->getId())){ $sender->sendMessage(new TranslationContainer("commands.effect.success.removed", [$effect->getName(), $player->getDisplayName()])); } }else{ $effect->setDuration($duration)->setAmplifier($amplification); if($player->addEffect($effect)){ self::broadcastCommandMessage($sender, new TranslationContainer("%commands.effect.success", [$effect->getName(), $effect->getId(), $effect->getAmplifier(), $player->getDisplayName(), $effect->getDuration() / 20])); } } return true; } } setPermission("pocketmine.command.enchant"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } $player = $sender->getServer()->getPlayer($args[0]); if($player === null){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); return true; } $enchantId = $args[1]; $enchantLevel = isset($args[2]) ? (int) $args[2] : 1; $enchantment = Enchantment::getEnchantment($enchantId); if($enchantment->getId() === Enchantment::TYPE_INVALID){ $enchantment = Enchantment::getEnchantmentByName($enchantId); if($enchantment->getId() === Enchantment::TYPE_INVALID){ $sender->sendMessage(new TranslationContainer("commands.enchant.notFound", [$enchantment->getId()])); return true; } } $id = $enchantment->getId(); $maxLevel = Enchantment::getEnchantMaxLevel($id); if($enchantLevel > $maxLevel or $enchantLevel <= 0){ $sender->sendMessage(new TranslationContainer("commands.enchant.maxLevel", [$maxLevel])); return true; } $enchantment->setLevel($enchantLevel); $item = $player->getInventory()->getItemInHand(); if($item->getId() <= 0){ $sender->sendMessage(new TranslationContainer("commands.enchant.noItem")); return true; } if(Enchantment::getEnchantAbility($item) === 0){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.enchant.cantEnchant")); return true; } $item->addEnchantment($enchantment); $player->getInventory()->setItemInHand($item); self::broadcastCommandMessage($sender, new TranslationContainer("%commands.enchant.success")); return true; } }" ); $this->setPermission("pocketmine.command.extractphar"); } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->testPermission($sender)){ return false; } if(count($args) === 0){ $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); return true; } if(!isset($args[0]) or !file_exists($args[0])) return \false; $folderPath = $sender->getServer()->getPluginPath() . DIRECTORY_SEPARATOR . "GenisysPro" . DIRECTORY_SEPARATOR . basename($args[0]); if(file_exists($folderPath)){ $sender->sendMessage("Phar already exists, overwriting..."); }else{ @mkdir($folderPath); } $pharPath = "phar://$args[0]"; foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($pharPath)) as $fInfo){ $path = $fInfo->getPathname(); @mkdir(dirname($folderPath . str_replace($pharPath, "", $path)), 0755, true); file_put_contents($folderPath . str_replace($pharPath, "", $path), file_get_contents($path)); } $sender->sendMessage("Source Phar $args[0] has been created on $folderPath"); } } " ); $this->setPermission("pocketmine.command.extractplugin"); } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->testPermission($sender)){ return false; } if(count($args) === 0){ $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); return true; } $pluginName = trim(implode(" ", $args)); if($pluginName === "" or !(($plugin = Server::getInstance()->getPluginManager()->getPlugin($pluginName)) instanceof Plugin)){ $sender->sendMessage(TextFormat::RED . "Invalid plugin name, check the name case."); $this->sendPluginList($sender); return true; } $description = $plugin->getDescription(); if(!($plugin->getPluginLoader() instanceof PharPluginLoader)){ $sender->sendMessage(TextFormat::RED . "Plugin " . $description->getName() . " is not in Phar structure."); return true; } $folderPath = Server::getInstance()->getPluginPath() . DIRECTORY_SEPARATOR . "GenisysPro" . DIRECTORY_SEPARATOR . $description->getName() . "_v" . $description->getVersion() . "/"; if(file_exists($folderPath)){ $sender->sendMessage("Plugin already exists, overwriting..."); }else{ @mkdir($folderPath); } $reflection = new \ReflectionClass("pocketmine\\plugin\\PluginBase"); $file = $reflection->getProperty("file"); $file->setAccessible(true); $pharPath = str_replace("\\", "/", rtrim($file->getValue($plugin), "\\/")); foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($pharPath)) as $fInfo){ $path = $fInfo->getPathname(); @mkdir(dirname($folderPath . str_replace($pharPath, "", $path)), 0755, true); file_put_contents($folderPath . str_replace($pharPath, "", $path), file_get_contents($path)); } $license = " _____ _ _____ / ____| (_) | __ \ | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ __/ | |___/ "; $sender->sendMessage($license); $sender->sendMessage("Source plugin " . $description->getName() . " v" . $description->getVersion() . " has been created on " . $folderPath); return true; } /** * @param CommandSender $sender */ private function sendPluginList(CommandSender $sender){ $list = ""; foreach(($plugins = $sender->getServer()->getPluginManager()->getPlugins()) as $plugin){ if(strlen($list) > 0){ $list .= TextFormat::WHITE . ", "; } $list .= $plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED; $list .= $plugin->getDescription()->getFullName(); } $sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($plugins), $list])); } } setPermission("pocketmine.command.fill"); } /** * @param CommandSender $sender * @param string $label * @param array $args * * @return bool */ public function execute(CommandSender $sender, $label, array $args){ if(!$this->testPermission($sender)){ return true; } for($a = 0; $a < 6; $a++){ if(isset($args[$a])){ if(is_numeric($args[$a]) and is_integer($args[$a] + 0)){ $item = Item::fromString($args[6]); if($item instanceof ItemBlock){ $xmin = min($args[0] + 0, $args[3] + 0); $xmax = max($args[0] + 0, $args[3] + 0); $ymin = min($args[1] + 0, $args[4] + 0); $ymax = max($args[1] + 0, $args[4] + 0); $zmin = min($args[2] + 0, $args[5] + 0); $zmax = max($args[2] + 0, $args[5] + 0); $level = ($sender instanceof Player) ? $sender->getLevel() : $sender->getServer()->getDefaultLevel(); $n = 0; $nmax = ($xmax - $xmin + 1) * ($ymax - $ymin + 1) * ($zmax - $zmin + 1); for($x = $xmin; $x <= $xmax; $x++){ for($y = $ymin; $y <= $ymax; $y++){ for($z = $zmin; $z <= $zmax; $z++){ if($this->setBlock(new Vector3($x, $y, $z), $level, $item, isset($args[7]) ? $args[7] : 0)){ $n++; if(is_int($n / 10000)){ $sender->sendMessage(new TranslationContainer("$n out of $nmax blocks filled, now at $x $y $z", [])); } }else{ $sender->sendMessage(TextFormat::RED . new TranslationContainer("Error after filling $n out of $nmax blocks.", [])); return false; } } } } $sender->sendMessage(new TranslationContainer("Total of $n blocks filled.", [])); return true; } $sender->sendMessage(TextFormat::RED . new TranslationContainer($args[6] . " is not a valid block.", [])); return false; } $sender->sendMessage(TextFormat::RED . new TranslationContainer($args[$a] . " is not a valid coordinate.", [])); $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $sender->sendMessage(TextFormat::RED . new TranslationContainer("pocketmine.command.fill.missingParameter", [])); $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } return false; } /** * @param Vector3 $p * @param Level $lvl * @param ItemBlock $b * @param int $meta * * @return bool */ private function setBlock(Vector3 $p, Level $lvl, ItemBlock $b, int $meta = 0) : bool{ $block = $b->getBlock(); $block->setDamage($meta); $lvl->setBlock($p, $block); return true; } } setPermission("pocketmine.command.gamemode"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $gameMode = (int) Server::getGamemodeFromString($args[0]); if($gameMode === -1){ $sender->sendMessage("Unknown game mode"); return true; } $target = $sender; if(isset($args[1])){ $target = $sender->getServer()->getPlayer($args[1]); if($target === null){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); return true; } }elseif(!($sender instanceof Player)){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } if($target->setGamemode($gameMode) == false){ $sender->sendMessage(TextFormat::RED . "Game mode change for " . $target->getName() . " failed!"); }else{ if($target === $sender){ Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.self", [' ', ' ', Server::getGamemodeString($gameMode)])); }else{ $target->sendMessage(new TranslationContainer("gameMode.changed")); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.gamemode.success.other", [$target->getName(), Server::getGamemodeString($gameMode)])); } } return true; } } setPermission("pocketmine.command.gc"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $chunksCollected = 0; $entitiesCollected = 0; $tilesCollected = 0; $memory = memory_get_usage(); foreach($sender->getServer()->getLevels() as $level){ $diff = [count($level->getChunks()), count($level->getEntities()), count($level->getTiles())]; $level->doChunkGarbageCollection(); $level->unloadChunks(true); $chunksCollected += $diff[0] - count($level->getChunks()); $entitiesCollected += $diff[1] - count($level->getEntities()); $tilesCollected += $diff[2] - count($level->getTiles()); $level->clearCache(true); } $cyclesCollected = $sender->getServer()->getMemoryManager()->triggerGarbageCollector(); $sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "%pocketmine.command.gc.title" . TextFormat::GREEN . " ----"); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.gc.chunks" . TextFormat::RED . \number_format($chunksCollected)); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.gc.entities" . TextFormat::RED . \number_format($entitiesCollected)); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.gc.tiles" . TextFormat::RED . \number_format($tilesCollected)); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.gc.cycles" . TextFormat::RED . \number_format($cyclesCollected)); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.gc.memory" . TextFormat::RED . \number_format(\round((($memory - \memory_get_usage()) / 1024) / 1024, 2)) . " MB"); return true; } } setPermission("pocketmine.command.give"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } $player = $sender->getServer()->getPlayer($args[0]); $item = Item::fromString($args[1]); if(!isset($args[2])){ $item->setCount($item->getMaxStackSize()); }else{ $item->setCount((int) $args[2]); } if(isset($args[3])){ $tags = $exception = null; $data = implode(" ", array_slice($args, 3)); try{ $tags = NBT::parseJSON($data); }catch(\Throwable $ex){ $exception = $ex; } if(!($tags instanceof CompoundTag) or $exception !== null){ $sender->sendMessage(new TranslationContainer("commands.give.tagError", [$exception !== null ? $exception->getMessage() : "Invalid tag conversion"])); return true; } $item->setNamedTag($tags); } if($player instanceof Player){ if($item->getId() === 0){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.give.item.notFound", [$args[1]])); return true; } //TODO: overflow $player->getInventory()->addItem(clone $item); }else{ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); return true; } Command::broadcastCommandMessage($sender, new TranslationContainer("%commands.give.success", [ $item->getName() . " (" . $item->getId() . ":" . $item->getDamage() . ")", (string) $item->getCount(), $player->getName() ])); return true; } } setPermission("pocketmine.command.help"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $command = ""; $pageNumber = 1; }elseif(is_numeric($args[count($args) - 1])){ $pageNumber = (int) array_pop($args); if($pageNumber <= 0){ $pageNumber = 1; } $command = implode(" ", $args); }else{ $command = implode(" ", $args); $pageNumber = 1; } $pageHeight = $sender instanceof ConsoleCommandSender ? PHP_INT_MAX : 7; if($command === ""){ /** @var Command[][] $commands */ $commands = []; foreach($sender->getServer()->getCommandMap()->getCommands() as $command){ if($command->testPermissionSilent($sender)){ $commands[$command->getName()] = $command; } } ksort($commands, SORT_NATURAL | SORT_FLAG_CASE); $commands = array_chunk($commands, $pageHeight); $pageNumber = (int) min(count($commands), $pageNumber); if($pageNumber < 1){ $pageNumber = 1; } $sender->sendMessage(new TranslationContainer("commands.help.header", [$pageNumber, count($commands)])); if(isset($commands[$pageNumber - 1])){ foreach($commands[$pageNumber - 1] as $command){ $sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::WHITE . $command->getDescription()); } } return true; }else{ if(($cmd = $sender->getServer()->getCommandMap()->getCommand(strtolower($command))) instanceof Command){ if($cmd->testPermissionSilent($sender)){ $message = TextFormat::YELLOW . "--------- " . TextFormat::WHITE . " Help: /" . $cmd->getName() . TextFormat::YELLOW . " ---------\n"; $message .= TextFormat::GOLD . "Description: " . TextFormat::WHITE . $cmd->getDescription() . "\n"; $message .= TextFormat::GOLD . "Usage: " . TextFormat::WHITE . implode("\n" . TextFormat::WHITE, explode("\n", $cmd->getUsage())) . "\n"; $sender->sendMessage($message); return true; } } $sender->sendMessage(TextFormat::RED . "No help for " . strtolower($command)); return true; } } }setPermission("pocketmine.command.kick"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $name = array_shift($args); $reason = trim(implode(" ", $args)); if(($player = $sender->getServer()->getPlayer($name)) instanceof Player){ $player->kick($reason); if(strlen($reason) >= 1){ Command::broadcastCommandMessage($sender, new TranslationContainer("commands.kick.success.reason", [$player->getName(), $reason])); }else{ Command::broadcastCommandMessage($sender, new TranslationContainer("commands.kick.success", [$player->getName()])); } }else{ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); } return true; } } setPermission("pocketmine.command.kill.self;pocketmine.command.kill.other"); /* fix in Player.php /kill is everybody can use */ } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) >= 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if(count($args) === 1){ if(!$sender->hasPermission("pocketmine.command.kill.other")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } $player = $sender->getServer()->getPlayer($args[0]); if($player instanceof Player){ $sender->getServer()->getPluginManager()->callEvent($ev = new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, 1000)); if($ev->isCancelled()){ return true; } $player->setLastDamageCause($ev); $player->setHealth(0); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.kill.successful", [$player->getName()])); }else{ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); } return true; } if($sender instanceof Player){ if(!$sender->hasPermission("pocketmine.command.kill.self")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } $sender->getServer()->getPluginManager()->callEvent($ev = new EntityDamageEvent($sender, EntityDamageEvent::CAUSE_SUICIDE, 1000)); if($ev->isCancelled()){ return true; } $sender->setLastDamageCause($ev); $sender->setHealth(0); $sender->sendMessage(new TranslationContainer("commands.kill.successful", [$sender->getName()])); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } return true; } } setPermission("pocketmine.command.list"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $online = ""; $onlineCount = 0; foreach($sender->getServer()->getOnlinePlayers() as $player){ if($player->isOnline() and (!($sender instanceof Player) or $sender->canSee($player))){ $online .= $player->getDisplayName() . ", "; ++$onlineCount; } } $sender->sendMessage(new TranslationContainer("commands.players.list", [$onlineCount, $sender->getServer()->getMaxPlayers()])); $sender->sendMessage(substr($online, 0, -2)); return true; } }" ); $this->setPermission("pocketmine.command.loadplugin"); } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->testPermission($sender)){ return false; } if(count($args) === 0){ $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); return true; } if(!isset($args[0])) return false; $plugin = $sender->getServer()->getPluginManager()->loadPlugin($sender->getServer()->getPluginPath() . DIRECTORY_SEPARATOR . $args[0]); if($plugin != null){ $sender->getServer()->getPluginManager()->enablePlugin($plugin); return true; } return false; } } " ); $this->setPermission("pocketmine.command.lvdat"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return false; } $levname = array_shift($args); if($levname == ""){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if(!$this->autoLoad($sender, $levname)){ $sender->sendMessage(new TranslationContainer("pocketmine.command.lvdat.nofound", [$levname])); return false; } $level = $sender->getServer()->getLevelByName($levname); if(!$level){ $sender->sendMessage(new TranslationContainer("pocketmine.command.lvdat.nofound", [$levname])); return false; } /** @var BaseLevelProvider $provider */ $provider = $level->getProvider(); $o = array_shift($args); $p = array_shift($args); switch($o){ case "fixname": $provider->getLevelData()->LevelName = new StringTag("LevelName", $level->getFolderName()); $sender->sendMessage(new TranslationContainer("pocketmine.command.lvdat.fixname", [$level->getFolderName()])); break; case "help": $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); $sender->sendMessage("/lvdat %commands.generic.level fixname"); $sender->sendMessage("/lvdat %commands.generic.level seed %commands.generic.seed"); $sender->sendMessage("/lvdat %commands.generic.level name %commands.generic.name"); $sender->sendMessage("/lvdat %commands.generic.level generator %commands.generic.generator"); $sender->sendMessage("/lvdat %commands.generic.level preset %pocketmine.command.lvdat.preset"); break; case "seed": if($p == ""){ $sender->sendMessage("%commands.generic.opt.missing"); return false; } $provider->setSeed($p); $sender->sendMessage(new TranslationContainer("pocketmine.command.lvdat.changed", [$level->getFolderName(), $o])); break; case "name": if($p == ""){ $sender->sendMessage("%commands.generic.opt.missing"); return false; } $provider->getLevelData()->LevelName = new StringTag("LevelName", $p); $sender->sendMessage(new TranslationContainer("pocketmine.command.lvdat.changed", [$level->getFolderName(), $o])); break; case "generator": if($p == ""){ $sender->sendMessage("%commands.generic.opt.missing"); return false; } $provider->getLevelData()->generatorName = new StringTag("generatorName", $p); $sender->sendMessage(new TranslationContainer("pocketmine.command.lvdat.changed", [$level->getFolderName(), $o])); break; case "preset": if($p == ""){ $sender->sendMessage("%commands.generic.opt.missing"); return false; } $provider->getLevelData()->generatorOptions = new StringTag("generatorOptions", $p); $sender->sendMessage(new TranslationContainer("pocketmine.command.lvdat.changed", [$level->getFolderName(), $o])); break; default: $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $provider->saveLevelData(); return true; } /** * @param CommandSender $c * @param $world * * @return bool */ public function autoLoad(CommandSender $c, $world){ if($c->getServer()->isLevelLoaded($world)) return true; if(!$c->getServer()->isLevelGenerated($world)){ return false; } $c->getServer()->loadLevel($world); return $c->getServer()->isLevelLoaded($world); } } " ); $this->setPermission("pocketmine.command.makeplugin"); } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->testPermission($sender)){ return false; } if(count($args) === 0){ $sender->sendMessage(TextFormat::RED . "Usage: " . $this->usageMessage); return true; } $pluginName = trim(implode(" ", $args)); if($pluginName === "" or !(($plugin = Server::getInstance()->getPluginManager()->getPlugin($pluginName)) instanceof Plugin)){ $sender->sendMessage(TextFormat::RED . "Invalid plugin name, check the name case."); $this->sendPluginList($sender); return true; } $description = $plugin->getDescription(); if(!($plugin->getPluginLoader() instanceof FolderPluginLoader)){ $sender->sendMessage(TextFormat::RED . "Plugin " . $description->getName() . " is not in folder structure."); return true; } $pharPath = Server::getInstance()->getPluginPath() . DIRECTORY_SEPARATOR . "GenisysPro" . DIRECTORY_SEPARATOR . $description->getName() . "_v" . $description->getVersion() . "_" . date("Y-m-d") . ".phar"; if(file_exists($pharPath)){ $sender->sendMessage("Phar plugin already exists, overwriting..."); @unlink($pharPath); } $phar = new \Phar($pharPath); $phar->setMetadata([ "name" => $description->getName(), "version" => $description->getVersion(), "main" => $description->getMain(), "api" => $description->getCompatibleApis(), "depend" => $description->getDepend(), "description" => $description->getDescription(), "authors" => $description->getAuthors(), "website" => $description->getWebsite(), "creationDate" => time() ]); if($description->getName() === "DevTools"){ $phar->setStub('setStub('getName() . ' v' . $description->getVersion() . '\nThis file has been generated using GenisysPro at ' . date("r") . '\n----------------\n";if(extension_loaded("phar")){$phar = new \Phar(__FILE__);foreach($phar->getMetadata() as $key => $value){echo ucfirst($key).": ".(is_array($value) ? implode(", ", $value):$value)."\n";}} __HALT_COMPILER();'); } $phar->setSignatureAlgorithm(\Phar::SHA1); $reflection = new \ReflectionClass("pocketmine\\plugin\\PluginBase"); $file = $reflection->getProperty("file"); $file->setAccessible(true); $filePath = rtrim(str_replace("\\", "/", $file->getValue($plugin)), "/") . "/"; $phar->startBuffering(); foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($filePath)) as $file){ $path = ltrim(str_replace(["\\", $filePath], ["/", ""], $file), "/"); if($path{0} === "." or strpos($path, "/.") !== false){ continue; } $phar->addFile($file, $path); $sender->sendMessage("[GenisysPro] Adding $path"); } foreach($phar as $file => $finfo){ /** @var \PharFileInfo $finfo */ if($finfo->getSize() > (1024 * 512)){ $finfo->compress(\Phar::GZ); } } $phar->stopBuffering(); $license = " _____ _ _____ / ____| (_) | __ \ | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ __/ | |___/ "; $sender->sendMessage($license); $sender->sendMessage("Phar plugin " . $description->getName() . " v" . $description->getVersion() . " has been created on " . $pharPath); return true; } /** * @param CommandSender $sender */ private function sendPluginList(CommandSender $sender){ $list = ""; foreach(($plugins = $sender->getServer()->getPluginManager()->getPlugins()) as $plugin){ if(strlen($list) > 0){ $list .= TextFormat::WHITE . ", "; } $list .= $plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED; $list .= $plugin->getDescription()->getFullName(); } $sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($plugins), $list])); } } setPermission("pocketmine.command.makeserver"); } /** * @param CommandSender $sender * @param string $commandLabel * @param array $args * * @return bool */ public function execute(CommandSender $sender, $commandLabel, array $args){ if(!$this->testPermission($sender)){ return false; } $server = $sender->getServer(); $pharPath = Server::getInstance()->getPluginPath() . DIRECTORY_SEPARATOR . "GenisysPro" . DIRECTORY_SEPARATOR . $server->getName() . "_" . $server->getPocketMineVersion() . "_" . date("Y-m-d") . ".phar"; if(file_exists($pharPath)){ $sender->sendMessage("Phar file already exists, overwriting..."); @unlink($pharPath); } $phar = new \Phar($pharPath); $phar->setMetadata([ "name" => $server->getName(), "version" => $server->getPocketMineVersion(), "api" => $server->getApiVersion(), "minecraft" => $server->getVersion(), "protocol" => ProtocolInfo::CURRENT_PROTOCOL, "creationDate" => time() ]); $phar->setStub('setSignatureAlgorithm(\Phar::SHA1); $phar->startBuffering(); $filePath = substr(\pocketmine\PATH, 0, 7) === "phar://" ? \pocketmine\PATH : realpath(\pocketmine\PATH) . "/"; $filePath = rtrim(str_replace("\\", "/", $filePath), "/") . "/"; if(is_dir($filePath . ".git")){ // Add some Git files as they are required in getting GIT_COMMIT foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($filePath . ".git")) as $file){ $path = ltrim(str_replace(["\\", $filePath], ["/", ""], $file), "/"); if((strpos($path, ".git/HEAD") === false and strpos($path, ".git/refs/heads") === false) or strpos($path, "/.") !== false){ continue; } $phar->addFile($file, $path); $sender->sendMessage("[GenisysPro] Adding $path"); } } foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($filePath . "src")) as $file){ $path = ltrim(str_replace(["\\", $filePath], ["/", ""], $file), "/"); if($path{0} === "." or strpos($path, "/.") !== false or substr($path, 0, 4) !== "src/"){ continue; } $phar->addFile($file, $path); $sender->sendMessage("[GenisysPro] Adding $path"); } foreach($phar as $file => $finfo){ /** @var \PharFileInfo $finfo */ if($finfo->getSize() > (1024 * 512)){ $finfo->compress(\Phar::GZ); } } $phar->stopBuffering(); $license = " _____ _ _____ / ____| (_) | __ \ | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ __/ | |___/ "; $sender->sendMessage($license); $sender->sendMessage($server->getName() . " " . $server->getPocketMineVersion() . " Phar file has been created on " . $pharPath); return true; } } setPermission("pocketmine.command.me"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $sender->getServer()->broadcastMessage(new TranslationContainer("chat.type.emote", [$sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::WHITE . implode(" ", $args)])); return true; } }setPermission("pocketmine.command.op.give"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $name = array_shift($args); $player = $sender->getServer()->getOfflinePlayer($name); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.op.success", [$player->getName()])); if($player instanceof Player){ $player->sendMessage(TextFormat::GRAY . "You are now op!"); } $player->setOp(true); return true; } }setPermission("pocketmine.command.pardoncid"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) !== 1){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $sender->getServer()->getCIDBans()->remove($args[0]); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.unbancid.success", [$args[0]])); return true; } } setPermission("pocketmine.command.unban.player"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) !== 1){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $sender->getServer()->getNameBans()->remove($args[0]); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.unban.success", [$args[0]])); return true; } } setPermission("pocketmine.command.unban.ip"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) !== 1){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if(preg_match("/^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$/", $args[0])){ $sender->getServer()->getIPBans()->remove($args[0]); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.unbanip.success", [$args[0]])); $sender->getServer()->getNetwork()->unblockAddress($args[0]); }else{ $sender->sendMessage(new TranslationContainer("commands.unbanip.invalid")); } return true; } } setPermission("pocketmine.command.particle"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 7){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } if($sender instanceof Player){ $level = $sender->getLevel(); }else{ $level = $sender->getServer()->getDefaultLevel(); } $name = strtolower($args[0]); $pos = new Vector3((float) $args[1], (float) $args[2], (float) $args[3]); $xd = (float) $args[4]; $yd = (float) $args[5]; $zd = (float) $args[6]; $count = isset($args[7]) ? max(1, (int) $args[7]) : 1; $data = isset($args[8]) ? (int) $args[8] : null; $particle = $this->getParticle($name, $pos, $xd, $yd, $zd, $data); if($particle === null){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.particle.notFound", [$name])); return true; } $sender->sendMessage(new TranslationContainer("commands.particle.success", [$name, $count])); $random = new Random((int) (microtime(true) * 1000) + mt_rand()); for($i = 0; $i < $count; ++$i){ $particle->setComponents( $pos->x + $random->nextSignedFloat() * $xd, $pos->y + $random->nextSignedFloat() * $yd, $pos->z + $random->nextSignedFloat() * $zd ); $level->addParticle($particle); } return true; } /** * @param $name * @param Vector3 $pos * @param $xd * @param $yd * @param $zd * @param $data * * @return Particle */ private function getParticle($name, Vector3 $pos, $xd, $yd, $zd, $data){ switch($name){ case "explode": return new ExplodeParticle($pos); case "hugeexplosion": return new HugeExplodeParticle($pos); case "hugeexplosionseed": return new HugeExplodeSeedParticle($pos); case "bubble": return new BubbleParticle($pos); case "splash": return new SplashParticle($pos); case "wake": case "water": return new WaterParticle($pos); case "crit": return new CriticalParticle($pos); case "smoke": return new SmokeParticle($pos, $data ?? 0); case "spell": return new EnchantParticle($pos); case "instantspell": return new InstantEnchantParticle($pos); case "dripwater": return new WaterDripParticle($pos); case "driplava": return new LavaDripParticle($pos); case "townaura": case "spore": return new SporeParticle($pos); case "portal": return new PortalParticle($pos); case "flame": return new FlameParticle($pos); case "lava": return new LavaParticle($pos); case "reddust": return new RedstoneParticle($pos, $data ?? 1); case "snowballpoof": return new ItemBreakParticle($pos, Item::get(Item::SNOWBALL)); case "slime": return new ItemBreakParticle($pos, Item::get(Item::SLIMEBALL)); case "itembreak": if($data !== null and $data !== 0){ return new ItemBreakParticle($pos, $data); } break; case "terrain": if($data !== null and $data !== 0){ return new TerrainParticle($pos, $data); } break; case "heart": return new HeartParticle($pos, $data ?? 0); case "ink": return new InkParticle($pos, $data ?? 0); case "droplet": return new RainSplashParticle($pos); case "enchantmenttable": return new EnchantmentTableParticle($pos); case "happyvillager": return new HappyVillagerParticle($pos); case "angryvillager": return new AngryVillagerParticle($pos); case "forcefield": return new BlockForceFieldParticle($pos, $data ?? 0); } if(substr($name, 0, 10) === "iconcrack_"){ $d = explode("_", $name); if(count($d) === 3){ return new ItemBreakParticle($pos, Item::get((int) $d[1], (int) $d[2])); } }elseif(substr($name, 0, 11) === "blockcrack_"){ $d = explode("_", $name); if(count($d) === 2){ return new TerrainParticle($pos, Block::get($d[1] & 0xff, $d[1] >> 12)); } }elseif(substr($name, 0, 10) === "blockdust_"){ $d = explode("_", $name); if(count($d) >= 4){ return new DustParticle($pos, $d[1] & 0xff, $d[2] & 0xff, $d[3] & 0xff, isset($d[4]) ? $d[4] & 0xff : 255); } } return null; } }setPermission("pocketmine.command.plugins"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $this->sendPluginList($sender); return true; } /** * @param CommandSender $sender */ private function sendPluginList(CommandSender $sender){ $list = ""; foreach(($plugins = $sender->getServer()->getPluginManager()->getPlugins()) as $plugin){ if(strlen($list) > 0){ $list .= TextFormat::WHITE . ", "; } $list .= $plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED; $list .= $plugin->getDescription()->getFullName(); } $sender->sendMessage(new TranslationContainer("pocketmine.command.plugins.success", [count($plugins), $list])); } } setPermission("pocketmine.command.reload"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } Command::broadcastCommandMessage($sender, new TranslationContainer(TextFormat::YELLOW . "%pocketmine.command.reload.reloading")); $sender->getServer()->reload(); Command::broadcastCommandMessage($sender, new TranslationContainer(TextFormat::YELLOW . "%pocketmine.command.reload.reloaded")); return true; } } setPermission("pocketmine.command.save.perform"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.start")); foreach($sender->getServer()->getOnlinePlayers() as $player){ $player->save(); } foreach($sender->getServer()->getLevels() as $level){ $level->save(true); } Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.success")); return true; } }setPermission("pocketmine.command.save.disable"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $sender->getServer()->setAutoSave(false); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.disabled")); return true; } }setPermission("pocketmine.command.save.enable"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $sender->getServer()->setAutoSave(true); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.save.enabled")); return true; } }setPermission("pocketmine.command.say"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $sender->getServer()->broadcastMessage(new TranslationContainer(TextFormat::LIGHT_PURPLE . "%chat.type.announcement", [$sender instanceof Player ? $sender->getDisplayName() : ($sender instanceof ConsoleCommandSender ? "Server" : $sender->getName()), TextFormat::LIGHT_PURPLE . implode(" ", $args)])); return true; } } setPermission("pocketmine.command.seed"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if($sender instanceof Player){ $seed = $sender->getLevel()->getSeed(); }else{ $seed = $sender->getServer()->getDefaultLevel()->getSeed(); } $sender->sendMessage(new TranslationContainer("commands.seed.success", [$seed])); return true; } }setPermission("pocketmine.command.setblock"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 4 or count($args) > 5){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $itemblock = Item::fromString($args[3]); if($itemblock instanceof ItemBlock){ $block = $itemblock->getBlock(); if(isset($args[4]) and is_numeric($args[4])) $block->setDamage((int) $args[4]); $x = $args[0]; $y = $args[1]; $z = $args[2]; if($x{0} === "~"){ if((is_numeric(trim($x, "~")) or trim($x, "~") === "") and ($sender instanceof Player)) $x = (int) round(trim($x, "~") + $sender->x); }elseif(is_numeric($x)){ $x = (int) round($x); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if($y{0} === "~"){ if((is_numeric(trim($y, "~")) or trim($y, "~") === "") and ($sender instanceof Player)) $y = (int) round(trim($y, "~") + $sender->y); if($y < 0 or $y > 256) return false; }elseif(is_numeric($y)){ $y = (int) round($y); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if($z{0} === "~"){ if((is_numeric(trim($z, "~")) or trim($z, "~") === "") and ($sender instanceof Player)) $z = (int) round(trim($z, "~") + $sender->z); }elseif(is_numeric($z)){ $z = (int) round($z); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if(!(is_integer($x) and is_integer($y) and is_integer($z))){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $pos = new Vector3($x, $y, $z); if($pos instanceof Vector3){ $level = ($sender instanceof Player) ? $sender->getLevel() : $sender->getServer()->getDefaultLevel(); if($level->setBlock($pos, $block)){ $sender->sendMessage("Successfully set the block at ($x, $y, $z) to block $args[3]"); return true; }else{ $sender->sendMessage(TextFormat::RED . new TranslationContainer("commands.generic.exception", [])); return false; } } }else{ $sender->sendMessage(TextFormat::RED . new TranslationContainer("command.setblock.invalidBlock", [])); return false; } return true; } } setPermission("pocketmine.command.setworldspawn"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0){ if($sender instanceof Player){ $level = $sender->getLevel(); $pos = (new Vector3($sender->x, $sender->y, $sender->z))->round(); }else{ $sender->sendMessage(TextFormat::RED . "You can only perform this command as a player"); return true; } }elseif(count($args) === 3){ $level = $sender->getServer()->getDefaultLevel(); $pos = new Vector3($this->getInteger($sender, $args[0]), $this->getInteger($sender, $args[1]), $this->getInteger($sender, $args[2])); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } $level->setSpawnLocation($pos); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.setworldspawn.success", [round($pos->x, 2), round($pos->y, 2), round($pos->z, 2)])); return true; } } setPermission("pocketmine.command.spawnpoint"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $target = null; if(count($args) === 0){ if($sender instanceof Player){ $target = $sender; }else{ $sender->sendMessage(TextFormat::RED . "Please provide a player!"); return true; } }else{ $target = $sender->getServer()->getPlayer($args[0]); if($target === null){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); return true; } } $level = $target->getLevel(); if(count($args) === 4){ if($level !== null){ $pos = $sender instanceof Player ? $sender->getPosition() : $level->getSpawnLocation(); $x = (int) $this->getRelativeDouble($pos->x, $sender, $args[1]); $y = $this->getRelativeDouble($pos->y, $sender, $args[2], 0, 128); $z = $this->getRelativeDouble($pos->z, $sender, $args[3]); $target->setSpawn(new Position($x, $y, $z, $level)); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.spawnpoint.success", [$target->getName(), round($x, 2), round($y, 2), round($z, 2)])); return true; } }elseif(count($args) <= 1){ if($sender instanceof Player){ $pos = new Position((int) $sender->x, (int) $sender->y, (int) $sender->z, $sender->getLevel()); $target->setSpawn($pos); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.spawnpoint.success", [$target->getName(), round($pos->x, 2), round($pos->y, 2), round($pos->z, 2)])); return true; }else{ $sender->sendMessage(TextFormat::RED . "Please provide a player!"); return true; } } $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } } setPermission("pocketmine.command.status"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $mUsage = Utils::getMemoryUsage(true); $rUsage = Utils::getRealMemoryUsage(); $server = $sender->getServer(); $onlineCount = 0; foreach($sender->getServer()->getOnlinePlayers() as $player){ if($player->isOnline() and (!($sender instanceof Player) or $sender->canSee($player))){ ++$onlineCount; } } $sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "%pocketmine.command.status.title" . TextFormat::GREEN . " ----"); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.player" . TextFormat::GREEN . " " . $onlineCount . "/" . $sender->getServer()->getMaxPlayers()); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.uptime " . TextFormat::RED . $sender->getServer()->getUptime()); $tpsColor = TextFormat::GREEN; if($server->getTicksPerSecondAverage() < 10){ $tpsColor = TextFormat::GOLD; }elseif($server->getTicksPerSecondAverage() < 1){ $tpsColor = TextFormat::RED; } $tpsColour = TextFormat::GREEN; if($server->getTicksPerSecond() < 10){ $tpsColour = TextFormat::GOLD; }elseif($server->getTicksPerSecond() < 1){ $tpsColour = TextFormat::RED; } $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.AverageTPS " . $tpsColor . $server->getTicksPerSecondAverage() . " (" . $server->getTickUsageAverage() . "%)"); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.CurrentTPS " . $tpsColour . $server->getTicksPerSecond() . " (" . $server->getTickUsage() . "%)"); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Networkupload " . TextFormat::RED . \round($server->getNetwork()->getUpload() / 1024, 2) . " kB/s"); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Networkdownload " . TextFormat::RED . \round($server->getNetwork()->getDownload() / 1024, 2) . " kB/s"); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Threadcount " . TextFormat::RED . Utils::getThreadCount()); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Mainmemory " . TextFormat::RED . number_format(round(($mUsage[0] / 1024) / 1024, 2)) . " MB."); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Totalmemory " . TextFormat::RED . number_format(round(($mUsage[1] / 1024) / 1024, 2)) . " MB."); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Totalvirtualmemory " . TextFormat::RED . number_format(round(($mUsage[2] / 1024) / 1024, 2)) . " MB."); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Heapmemory " . TextFormat::RED . number_format(round(($rUsage[0] / 1024) / 1024, 2)) . " MB."); $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Maxmemorysystem " . TextFormat::RED . number_format(round(($mUsage[2] / 1024) / 1024, 2)) . " MB."); if($server->getProperty("memory.global-limit") > 0){ $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.Maxmemorymanager " . TextFormat::RED . number_format(round($server->getProperty("memory.global-limit"), 2)) . " MB."); } foreach($server->getLevels() as $level){ $sender->sendMessage(TextFormat::GOLD . "%pocketmine.command.status.World \"" . $level->getFolderName() . "\"" . ($level->getFolderName() !== $level->getName() ? " (" . $level->getName() . ")" : "") . ": " . TextFormat::RED . number_format(count($level->getChunks())) . TextFormat::GREEN . " %pocketmine.command.status.chunks " . TextFormat::RED . number_format(count($level->getEntities())) . TextFormat::GREEN . " %pocketmine.command.status.entities " . TextFormat::RED . number_format(count($level->getTiles())) . TextFormat::GREEN . " %pocketmine.command.status.tiles " . "%pocketmine.command.status.Time " . (($level->getTickRate() > 1 or $level->getTickRateTime() > 40) ? TextFormat::RED : TextFormat::YELLOW) . round($level->getTickRateTime(), 2) . "%pocketmine.command.status.ms" . ($level->getTickRate() > 1 ? " (tick rate " . $level->getTickRate() . ")" : "") ); } return true; } } setPermission("pocketmine.command.stop"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } $restart = false; if(isset($args[0])){ if($args[0] == 'force'){ $restart = true; array_shift($args); }else{ $restart = false; } } Command::broadcastCommandMessage($sender, new TranslationContainer("commands.stop.start")); $msg = implode(" ", $args); $sender->getServer()->shutdown($restart, $msg); return true; } }setPermission("pocketmine.command.summon"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) != 1 and count($args) != 4 and count($args) != 5){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } $x = 0; $y = 0; $z = 0; if(count($args) == 4 or count($args) == 5){ //position is set //TODO:simpilify them to one piece of code //Code for setting $x if(is_numeric($args[1])){ //x is given directly $x = $args[1]; }elseif(strcmp($args[1], "~") >= 0){ //x is given with a "~" $offset_x = trim($args[1], "~"); if($sender instanceof Player){ //using in-game $x = is_numeric($offset_x) ? ($sender->x + $offset_x) : $sender->x; }else{ //using in console $sender->sendMessage(TextFormat::RED . "You must specify a position where the entity is spawned to when using in console"); return false; } }else{ //other circumstances $sender->sendMessage(TextFormat::RED . "Argument error"); return false; } //Code for setting $y if(is_numeric($args[2])){ //y is given directly $y = $args[2]; }elseif(strcmp($args[2], "~") >= 0){ //y is given with a "~" $offset_y = trim($args[2], "~"); if($sender instanceof Player){ //using in-game $y = is_numeric($offset_y) ? ($sender->y + $offset_y) : $sender->y; $y = min(128, max(0, $y)); }else{ //using in console $sender->sendMessage(TextFormat::RED . "You must specify a position where the entity is spawned to when using in console"); return false; } }else{ //other circumstances $sender->sendMessage(TextFormat::RED . "Argument error"); return false; } //Code for setting $z if(is_numeric($args[3])){ //z is given directly $z = $args[3]; }elseif(strcmp($args[3], "~") >= 0){ //z is given with a "~" $offset_z = trim($args[3], "~"); if($sender instanceof Player){ //using in-game $z = is_numeric($offset_z) ? ($sender->z + $offset_z) : $sender->z; }else{ //using in console $sender->sendMessage(TextFormat::RED . "You must specify a position where the entity is spawned to when using in console"); return false; } }else{ //other circumstances $sender->sendMessage(TextFormat::RED . "Argument error"); return false; } } //finish setting the location if(count($args) == 1){ if($sender instanceof Player){ $x = $sender->x; $y = $sender->y; $z = $sender->z; }else{ $sender->sendMessage(TextFormat::RED . "You must specify a position where the entity is spawned to when using in console"); return false; } } //finish setting the location $entity = null; $type = $args[0]; $level = ($sender instanceof Player) ? $sender->getLevel() : $sender->getServer()->getDefaultLevel(); $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $x), new DoubleTag("", $y), new DoubleTag("", $z) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", lcg_value() * 360), new FloatTag("", 0) ]), ]); if(count($args) == 5 and $args[4]{0} == "{"){//Tags are found $nbtExtra = NBT::parseJSON($args[4]); $nbt = NBT::combineCompoundTags($nbt, $nbtExtra, true); } $entity = Entity::createEntity($type, $level, $nbt); if($entity instanceof Entity){ $entity->spawnToAll(); $sender->sendMessage("Successfully spawned entity $type at ($x, $y, $z)"); return true; }else{ $sender->sendMessage(TextFormat::RED . "An error occurred when spawning the entity $type"); return false; } } } setPermission("pocketmine.command.teleport"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 1 or count($args) > 6){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } $target = null; $origin = $sender; if(count($args) === 1 or count($args) === 3 or count($args) === 5){ if($sender instanceof Player){ $target = $sender; }else{ $sender->sendMessage(TextFormat::RED . "Please provide a player!"); return true; } if(count($args) === 1){ $target = $sender->getServer()->getPlayer($args[0]); if($target === null){ $sender->sendMessage(TextFormat::RED . "Can't find player " . $args[0]); return true; } } }else{ $target = $sender->getServer()->getPlayer($args[0]); if($target === null){ $sender->sendMessage(TextFormat::RED . "Can't find player " . $args[0]); return true; } if(count($args) === 2){ $origin = $target; $target = $sender->getServer()->getPlayer($args[1]); if($target === null){ $sender->sendMessage(TextFormat::RED . "Can't find player " . $args[1]); return true; } } } if(count($args) < 3){ $origin->teleport($target); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.tp.success", [$origin->getName(), $target->getName()])); return true; }elseif($target->getLevel() !== null){ if(count($args) === 4 or count($args) === 6){ $pos = 1; }else{ $pos = 0; } $x = $this->getRelativeDouble($target->x, $sender, $args[$pos++]); $y = $this->getRelativeDouble($target->y, $sender, $args[$pos++], 0, 256); $z = $this->getRelativeDouble($target->z, $sender, $args[$pos++]); $yaw = $target->getYaw(); $pitch = $target->getPitch(); if(count($args) === 6 or (count($args) === 5 and $pos === 3)){ $yaw = $args[$pos++]; $pitch = $args[$pos++]; } $target->teleport(new Vector3($x, $y, $z), $yaw, $pitch); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.tp.success.coordinates", [$target->getName(), round($x, 2), round($y, 2), round($z, 2)])); return true; } $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } } setPermission("pocketmine.command.tell"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $name = strtolower(array_shift($args)); $player = $sender->getServer()->getPlayer($name); if($player === $sender){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.message.sameTarget")); return true; } if($player instanceof Player){ $sender->sendMessage("[" . $sender->getName() . " -> " . $player->getDisplayName() . "] " . implode(" ", $args)); $player->sendMessage("[" . ($sender instanceof Player ? $sender->getDisplayName() : $sender->getName()) . " -> " . $player->getName() . "] " . implode(" ", $args)); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.player.notFound")); } return true; } } setPermission("pocketmine.command.time.add;pocketmine.command.time.set;pocketmine.command.time.start;pocketmine.command.time.stop"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(count($args) < 1){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if($args[0] === "start"){ if(!$sender->hasPermission("pocketmine.command.time.start")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } foreach($sender->getServer()->getLevels() as $level){ $level->checkTime(); $level->startTime(); $level->checkTime(); } Command::broadcastCommandMessage($sender, "Restarted the time"); return true; }elseif($args[0] === "stop"){ if(!$sender->hasPermission("pocketmine.command.time.stop")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } foreach($sender->getServer()->getLevels() as $level){ $level->checkTime(); $level->stopTime(); $level->checkTime(); } Command::broadcastCommandMessage($sender, "Stopped the time"); return true; }elseif($args[0] === "query"){ if(!$sender->hasPermission("pocketmine.command.time.query")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } if($sender instanceof Player){ $level = $sender->getLevel(); }else{ $level = $sender->getServer()->getDefaultLevel(); } $sender->sendMessage(new TranslationContainer("commands.time.query", [$level->getTime()])); return true; } if(count($args) < 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if($args[0] === "set"){ if(!$sender->hasPermission("pocketmine.command.time.set")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } if($args[1] === "day"){ $value = 0; }elseif($args[1] === "night"){ $value = Level::TIME_NIGHT; }else{ $value = $this->getInteger($sender, $args[1], 0); } foreach($sender->getServer()->getLevels() as $level){ $level->checkTime(); $level->setTime($value); $level->checkTime(); } Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.set", [$value])); }elseif($args[0] === "add"){ if(!$sender->hasPermission("pocketmine.command.time.add")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } $value = $this->getInteger($sender, $args[1], 0); foreach($sender->getServer()->getLevels() as $level){ $level->checkTime(); $level->setTime($level->getTime() + $value); $level->checkTime(); } Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.added", [$value])); }else{ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); } return true; } }setPermission("pocketmine.command.timings"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) !== 1){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } $mode = strtolower($args[0]); if($mode === "on"){ $sender->getServer()->getPluginManager()->setUseTimings(true); TimingsHandler::reload(); $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.enable")); return true; }elseif($mode === "off"){ $sender->getServer()->getPluginManager()->setUseTimings(false); $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.disable")); return true; } if(!$sender->getServer()->getPluginManager()->useTimings()){ $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsDisabled")); return true; } $paste = $mode === "paste"; if($mode === "reset"){ TimingsHandler::reload(); $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.reset")); }elseif($mode === "merged" or $mode === "report" or $paste){ $sampleTime = microtime(true) - self::$timingStart; $index = 0; $timingFolder = $sender->getServer()->getDataPath() . "timings/"; if(!file_exists($timingFolder)){ mkdir($timingFolder, 0777); } $timings = $timingFolder . "timings.txt"; while(file_exists($timings)){ $timings = $timingFolder . "timings" . (++$index) . ".txt"; } $fileTimings = $paste ? fopen("php://temp", "r+b") : fopen($timings, "a+b"); TimingsHandler::printTimings($fileTimings); fwrite($fileTimings, "Sample time " . round($sampleTime * 1000000000) . " (" . $sampleTime . "s)" . PHP_EOL); if($paste){ fseek($fileTimings, 0); $data = [ "syntax" => "text", "poster" => $sender->getServer()->getName(), "content" => stream_get_contents($fileTimings) ]; $ch = curl_init("http://paste.ubuntu.com/"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_AUTOREFERER, false); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["User-Agent: " . $this->getName() . " " . $sender->getServer()->getPocketMineVersion()]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $data = curl_exec($ch); curl_close($ch); if(preg_match('#^Location: http://paste\\.ubuntu\\.com/([0-9]{1,})/#m', $data, $matches) == 0){ $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.pasteError")); return true; } $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsUpload", ["http://paste.ubuntu.com/" . $matches[1] . "/"])); $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsRead", ["http://" . $sender->getServer()->getProperty("timings.host", "timings.pmmp.io") . "/?url=" . $matches[1]])); fclose($fileTimings); }else{ fclose($fileTimings); $sender->sendMessage(new TranslationContainer("pocketmine.command.timings.timingsWrite", [$timings])); } } return true; } } setPermission("pocketmine.command.title"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if($sender instanceof Player){ if(!$this->testPermission($sender)){ return true; } if(count($args) <= 0){ $sender->sendMessage(new TranslationContainer("%pocketmine.command.title.usage")); return false; } } } } [port端口]", ["transferserver"] ); $this->setPermission("pocketmine.command.transfer"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ $address = null; $port = null; $player = null; if($sender instanceof Player){ if(!$this->testPermission($sender)){ return true; } if(count($args) <= 0){ $sender->sendMessage("Usage: /transferserver
[port]"); return false; } $address = strtolower($args[0]); $port = (isset($args[1]) && is_numeric($args[1]) ? $args[1] : 19132); $pk = new TransferPacket(); $pk->address = $address; $pk->port = $port; $sender->dataPacket($pk); return false; } if(count($args) <= 1){ $sender->sendMessage("Usage: /transferserver
[port]"); return false; } if(!($player = Server::getInstance()->getPlayer($args[0])) instanceof Player){ $sender->sendMessage("Player specified not found!"); return false; } $address = strtolower($args[1]); $port = (isset($args[2]) && is_numeric($args[2]) ? $args[2] : 19132); $sender->sendMessage("Sending " . $player->getName() . " to " . $address . ":" . $port); $pk = new TransferPacket(); $pk->address = $address; $pk->port = $port; $player->dataPacket($pk); } } $max){ $i = $max; } return $i; } /** * @param $original * @param CommandSender $sender * @param $input * @param int $min * @param int $max * * @return float|int */ protected function getRelativeDouble($original, CommandSender $sender, $input, $min = self::MIN_COORD, $max = self::MAX_COORD){ if($input{0} === "~"){ $value = $this->getDouble($sender, substr($input, 1)); return $original + $value; } return $this->getDouble($sender, $input, $min, $max); } /** * @param CommandSender $sender * @param $value * @param int $min * @param int $max * * @return float|int */ protected function getDouble(CommandSender $sender, $value, $min = self::MIN_COORD, $max = self::MAX_COORD){ $i = (double) $value; if($i < $min){ $i = $min; }elseif($i > $max){ $i = $max; } return $i; } }setPermission("pocketmine.command.version"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return \true; } if(\count($args) === 0){ $sender->sendMessage(new TranslationContainer("pocketmine.server.info.extended.title")); $sender->sendMessage(new TranslationContainer("pocketmine.server.info.extended1", [ $sender->getServer()->getName(), $sender->getServer()->getFormattedVersion("-"), $sender->getServer()->getShortGitCommit(), $sender->getServer()->getCodename() ])); $sender->sendMessage(new TranslationContainer("pocketmine.server.info.extended2", [ phpversion() ])); $sender->sendMessage(new TranslationContainer("pocketmine.server.info.extended3", [ $sender->getServer()->getApiVersion() ])); $sender->sendMessage(new TranslationContainer("pocketmine.server.info.extended4", [ $sender->getServer()->getVersion() ])); $sender->sendMessage(new TranslationContainer("pocketmine.server.info.extended5", [ ProtocolInfo::CURRENT_PROTOCOL ])); }else{ $pluginName = \implode(" ", $args); $exactPlugin = $sender->getServer()->getPluginManager()->getPlugin($pluginName); if($exactPlugin instanceof Plugin){ $this->describeToSender($exactPlugin, $sender); return \true; } $found = \false; $pluginName = \strtolower($pluginName); foreach($sender->getServer()->getPluginManager()->getPlugins() as $plugin){ if(\stripos($plugin->getName(), $pluginName) !== \false){ $this->describeToSender($plugin, $sender); $found = \true; } } if(!$found){ $sender->sendMessage(new TranslationContainer("pocketmine.command.version.noSuchPlugin")); } } return \true; } /** * @param Plugin $plugin * @param CommandSender $sender */ private function describeToSender(Plugin $plugin, CommandSender $sender){ $desc = $plugin->getDescription(); $sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::WHITE . " version " . TextFormat::DARK_GREEN . $desc->getVersion()); if($desc->getDescription() != \null){ $sender->sendMessage($desc->getDescription()); } if($desc->getWebsite() != \null){ $sender->sendMessage("Website: " . $desc->getWebsite()); } if(\count($authors = $desc->getAuthors()) > 0){ if(\count($authors) === 1){ $sender->sendMessage("Author: " . \implode(", ", $authors)); }else{ $sender->sendMessage("Authors: " . \implode(", ", $authors)); } } } } setPermission("pocketmine.command.weather"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 1){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if($sender instanceof Player){ $wea = Weather::getWeatherFromString($args[0]); if(!isset($args[1])) $duration = mt_rand(min($sender->getServer()->weatherRandomDurationMin, $sender->getServer()->weatherRandomDurationMax), max($sender->getServer()->weatherRandomDurationMin, $sender->getServer()->weatherRandomDurationMax)); else $duration = (int) $args[1]; if($wea >= 0 and $wea <= 3){ $sender->getLevel()->getWeather()->setWeather($wea, $duration); $sender->sendMessage(new TranslationContainer("pocketmine.command.weather.changed", [$sender->getLevel()->getFolderName()])); return true; /*if(WeatherManager::isRegistered($sender->getLevel())){ $sender->getLevel()->getWeather()->setWeather($wea, $duration); $sender->sendMessage(new TranslationContainer("pocketmine.command.weather.changed", [$sender->getLevel()->getFolderName()])); return true; }else{ $sender->sendMessage(new TranslationContainer("pocketmine.command.weather.noregistered", [$sender->getLevel()->getFolderName()])); return false; }*/ }else{ $sender->sendMessage(TextFormat::RED . "%pocketmine.command.weather.invalid"); return false; } } if(count($args) < 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } $level = $sender->getServer()->getLevelByName($args[0]); if(!$level instanceof Level){ $sender->sendMessage(TextFormat::RED . "%pocketmine.command.weather.invalid.level"); return false; } $wea = Weather::getWeatherFromString($args[1]); if(!isset($args[1])) $duration = mt_rand(min($sender->getServer()->weatherRandomDurationMin, $sender->getServer()->weatherRandomDurationMax), max($sender->getServer()->weatherRandomDurationMin, $sender->getServer()->weatherRandomDurationMax)); else $duration = (int) $args[1]; if($wea >= 0 and $wea <= 3){ $level->getWeather()->setWeather($wea, $duration); $sender->sendMessage(new TranslationContainer("pocketmine.command.weather.changed", [$level->getFolderName()])); return true; /*if(WeatherManager::isRegistered($level)){ $level->getWeather()->setWeather($wea, $duration); $sender->sendMessage(new TranslationContainer("pocketmine.command.weather.changed", [$level->getFolderName()])); return true; }else{ $sender->sendMessage(new TranslationContainer("pocketmine.command.weather.noregistered", [$level->getFolderName()])); return false; }*/ }else{ $sender->sendMessage(TextFormat::RED . "%pocketmine.command.weather.invalid"); return false; } } } setPermission("pocketmine.command.whitelist.reload;pocketmine.command.whitelist.enable;pocketmine.command.whitelist.disable;pocketmine.command.whitelist.list;pocketmine.command.whitelist.add;pocketmine.command.whitelist.remove"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) === 0 or count($args) > 2){ $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return true; } if(count($args) === 1){ if($this->badPerm($sender, strtolower($args[0]))){ return false; } switch(strtolower($args[0])){ case "reload": $sender->getServer()->reloadWhitelist(); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.whitelist.reloaded")); return true; case "on": $sender->getServer()->setConfigBool("white-list", true); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.whitelist.enabled")); return true; case "off": $sender->getServer()->setConfigBool("white-list", false); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.whitelist.disabled")); return true; case "list": $result = ""; $count = 0; foreach($sender->getServer()->getWhitelisted()->getAll(true) as $player){ $result .= $player . ", "; ++$count; } $sender->sendMessage(new TranslationContainer("commands.whitelist.list", [$count, $count])); $sender->sendMessage(substr($result, 0, -2)); return true; case "add": $sender->sendMessage(new TranslationContainer("commands.generic.usage", ["%commands.whitelist.add.usage"])); return true; case "remove": $sender->sendMessage(new TranslationContainer("commands.generic.usage", ["%commands.whitelist.remove.usage"])); return true; } }elseif(count($args) === 2){ if($this->badPerm($sender, strtolower($args[0]))){ return false; } switch(strtolower($args[0])){ case "add": $sender->getServer()->getOfflinePlayer($args[1])->setWhitelisted(true); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.whitelist.add.success", [$args[1]])); return true; case "remove": $sender->getServer()->getOfflinePlayer($args[1])->setWhitelisted(false); Command::broadcastCommandMessage($sender, new TranslationContainer("commands.whitelist.remove.success", [$args[1]])); return true; } } return true; } /** * @param CommandSender $sender * @param $perm * * @return bool */ private function badPerm(CommandSender $sender, $perm){ if(!$sender->hasPermission("pocketmine.command.whitelist.$perm")){ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.permission")); return true; } return false; } } setPermission("pocketmine.command.xp"); } /** * @param CommandSender $sender * @param string $currentAlias * @param array $args * * @return bool */ public function execute(CommandSender $sender, $currentAlias, array $args){ if(!$this->testPermission($sender)){ return true; } if(count($args) < 2){ if($sender instanceof ConsoleCommandSender){ $sender->sendMessage("You must specify a target player in the console"); return true; } $player = $sender; }else{ $player = $sender->getServer()->getPlayer($args[1]); } if($player instanceof Player){ $name = $player->getName(); if(count($args) < 1){ $player->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; } if(strcasecmp(substr($args[0], -1), "L") == 0){ $level = (int) rtrim($args[0], "Ll"); if($level > 0){ $player->addXpLevel((int) $level); $sender->sendMessage(new TranslationContainer("%commands.xp.success.levels", [$level, $name])); $player->getLevel()->broadcastLevelSoundEvent($player, LevelSoundEventPacket::SOUND_LEVELUP); return true; }elseif($level < 0){ $player->takeXpLevel((int) -$level); $sender->sendMessage(new TranslationContainer("%commands.xp.success.negative.levels", [-$level, $name])); return true; } }else{ if(($xp = (int) $args[0]) > 0){ //Set Experience $player->addXp((int) $args[0]); $player->getLevel()->addSound(new ExpPickupSound($player, mt_rand(0, 1000))); $sender->sendMessage(new TranslationContainer("%commands.xp.success", [$name, $args[0]])); return true; }elseif($xp < 0){ $sender->sendMessage(new TranslationContainer("%commands.xp.failure.withdrawXp")); return true; } } $sender->sendMessage(new TranslationContainer("commands.generic.usage", [$this->usageMessage])); return false; }else{ $sender->sendMessage(new TranslationContainer(TextFormat::RED . "%commands.generic.player.notFound")); return false; } } } getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_BABY); } }isCritical = (bool) $critical; if(!isset($nbt->Potion)){ $nbt->Potion = new ShortTag("Potion", 0); } parent::__construct($level, $nbt, $shootingEntity); $this->potionId = $this->namedtag["Potion"]; } /** * @return bool */ public function isCritical() : bool{ return $this->isCritical; } /** * @return int */ public function getPotionId() : int{ return $this->potionId; } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); if(!$this->hadCollision and $this->isCritical){ $this->level->addParticle(new CriticalParticle($this->add( $this->width / 2 + mt_rand(-100, 100) / 500, $this->height / 2 + mt_rand(-100, 100) / 500, $this->width / 2 + mt_rand(-100, 100) / 500))); }elseif($this->onGround){ $this->isCritical = false; } if($this->potionId != 0){ if(!$this->onGround or ($this->onGround and ($currentTick % 4) == 0)){ $color = Potion::getColor($this->potionId - 1); $this->level->addParticle(new MobSpellParticle($this->add( $this->width / 2 + mt_rand(-100, 100) / 500, $this->height / 2 + mt_rand(-100, 100) / 500, $this->width / 2 + mt_rand(-100, 100) / 500), $color[0], $color[1], $color[2])); } $hasUpdate = true; } if($this->age > 1200){ $this->kill(); $hasUpdate = true; } $this->timings->stopTiming(); return $hasUpdate; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = Arrow::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } $maxValue or $defaultValue > $maxValue or $defaultValue < $minValue){ throw new \InvalidArgumentException("Invalid ranges: min value: $minValue, max value: $maxValue, $defaultValue: $defaultValue"); } return self::$attributes[(int) $id] = new Attribute($id, $name, $minValue, $maxValue, $defaultValue, $shouldSend); } /** * @param $id * * @return null|Attribute */ public static function getAttribute($id){ return isset(self::$attributes[$id]) ? clone self::$attributes[$id] : null; } /** * @param $name * * @return null|Attribute */ public static function getAttributeByName($name){ foreach(self::$attributes as $a){ if($a->getName() === $name){ return clone $a; } } return null; } /** * Attribute constructor. * * @param $id * @param $name * @param $minValue * @param $maxValue * @param $defaultValue * @param bool $shouldSend */ public function __construct($id, $name, $minValue, $maxValue, $defaultValue, $shouldSend = true){ $this->id = (int) $id; $this->name = (string) $name; $this->minValue = (float) $minValue; $this->maxValue = (float) $maxValue; $this->defaultValue = (float) $defaultValue; $this->shouldSend = (bool) $shouldSend; $this->currentValue = $this->defaultValue; } /** * @return float */ public function getMinValue(){ return $this->minValue; } /** * @param $minValue * * @return $this */ public function setMinValue($minValue){ if($minValue > $this->getMaxValue()){ throw new \InvalidArgumentException("Value $minValue is bigger than the maxValue!"); } if($this->minValue != $minValue){ $this->desynchronized = true; $this->minValue = $minValue; } return $this; } /** * @return float */ public function getMaxValue(){ return $this->maxValue; } /** * @param $maxValue * * @return $this */ public function setMaxValue($maxValue){ if($maxValue < $this->getMinValue()){ throw new \InvalidArgumentException("Value $maxValue is bigger than the minValue!"); } if($this->maxValue != $maxValue){ $this->desynchronized = true; $this->maxValue = $maxValue; } return $this; } /** * @return float */ public function getDefaultValue(){ return $this->defaultValue; } /** * @param $defaultValue * * @return $this */ public function setDefaultValue($defaultValue){ if($defaultValue > $this->getMaxValue() or $defaultValue < $this->getMinValue()){ throw new \InvalidArgumentException("Value $defaultValue exceeds the range!"); } if($this->defaultValue !== $defaultValue){ $this->desynchronized = true; $this->defaultValue = $defaultValue; } return $this; } /** * @return float */ public function getValue(){ return $this->currentValue; } /** * @param $value * @param bool $fit * @param bool $shouldSend * * @return $this */ public function setValue($value, bool $fit = true, bool $shouldSend = false){ if($value > $this->getMaxValue() or $value < $this->getMinValue()){ if(!$fit){ Server::getInstance()->getLogger()->error("[Attribute / {$this->getName()}] Value $value exceeds the range!"); } $value = min(max($value, $this->getMinValue()), $this->getMaxValue()); } if($this->currentValue != $value){ $this->desynchronized = true; $this->currentValue = $value; } if($shouldSend){ $this->desynchronized = true; } return $this; } /** * @return string */ public function getName(){ return $this->name; } /** * @return int */ public function getId(){ return $this->id; } /** * @return bool */ public function isSyncable(){ return $this->shouldSend; } /** * @return bool */ public function isDesynchronized() : bool{ return $this->shouldSend and $this->desynchronized; } /** * @param bool $synced */ public function markSynchronized(bool $synced = true){ $this->desynchronized = !$synced; } } attributes[$attribute->getId()] = $attribute; } /** * @param int $id * * @return Attribute|null */ public function getAttribute(int $id){ return $this->attributes[$id] ?? null; } /** * @return array */ public function getAll() : array{ return $this->attributes; } /** * @return Attribute[] */ public function needSend() : array{ return array_filter($this->attributes, function(Attribute $attribute){ return $attribute->isSyncable() and $attribute->isDesynchronized(); }); } /** * @param mixed $offset * * @return bool */ public function offsetExists($offset){ return isset($this->attributes[$offset]); } /** * @param mixed $offset * * @return float */ public function offsetGet($offset){ return $this->attributes[$offset]->getValue(); } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value){ $this->attributes[$offset]->setValue($value); } /** * @param mixed $offset */ public function offsetUnset($offset){ throw new \RuntimeException("Could not unset an attribute from an attribute map"); } } setMaxHealth(6); parent::initEntity(); } /** * Bat constructor. * * @param Level $level * @param CompoundTag $nbt */ public function __construct(Level $level, CompoundTag $nbt){ if(!isset($nbt->isResting)){ $nbt->isResting = new ByteTag("isResting", 0); } parent::__construct($level, $nbt); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_RESTING, $this->isResting()); } /** * @return int */ public function isResting() : int{ return (int) $this->namedtag["isResting"]; } /** * @param bool $resting */ public function setResting(bool $resting){ $this->namedtag->isResting = new ByteTag("isResting", $resting ? 1 : 0); } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->age > 20 * 60 * 10){ $this->kill(); } return parent::onUpdate($currentTick); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Bat::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); $drops = [ItemItem::get(ItemItem::BLAZE_ROD, 0, mt_rand(0, 1 + $lootingL))]; return $drops; } } return []; } }WoodID)){ $nbt->WoodID = new IntTag("WoodID", 0); } parent::__construct($level, $nbt); $this->setDataProperty(self::DATA_VARIANT, self::DATA_TYPE_INT, $this->getWoodID()); } /** * @return int */ public function getWoodID() : int{ return (int) $this->namedtag["WoodID"]; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Boat::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = 0; $pk->speedY = 0; $pk->speedZ = 0; $pk->yaw = 0; $pk->pitch = 0; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ parent::attack($damage, $source); if(!$source->isCancelled()){ $pk = new EntityEventPacket(); $pk->eid = $this->id; $pk->event = EntityEventPacket::HURT_ANIMATION; foreach($this->getLevel()->getPlayers() as $player){ $player->dataPacket($pk); } } } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0 and !$this->justCreated){ return true; } $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = $this->entityBaseTick($tickDiff); if(!$this->level->getBlock(new Vector3($this->x, $this->y, $this->z))->getBoundingBox() == null or $this->isInsideOfWater()){ $this->motionY = 0.1; }else{ $this->motionY = -0.08; } $this->move($this->motionX, $this->motionY, $this->motionZ); $this->updateMovement(); if($this->linkedEntity == null or $this->linkedType = 0){ if($this->age > 1500){ $this->close(); $hasUpdate = true; //$this->scheduleUpdate(); $this->age = 0; } $this->age++; }else $this->age = 0; $this->timings->stopTiming(); return $hasUpdate or !$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } /** * @return array */ public function getDrops(){ return [ ItemItem::get(ItemItem::BOAT, 0, 1) ]; } /** * @return string */ public function getSaveId(){ $class = new \ReflectionClass(static::class); return $class->getShortName(); } } eid = $this->getId(); $pk->type = CaveSpider::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }eid = $this->getId(); $pk->type = Chicken::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ItemItem::get(ItemItem::FEATHER, 0, mt_rand(0, 2)) ]; $drops[] = ItemItem::get(ItemItem::RAW_CHICKEN, 0, 1); return $drops; } } eid = $this->getId(); $pk->type = Cow::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); $drops = [ItemItem::get(ItemItem::RAW_BEEF, 0, mt_rand(1, 3 + $lootingL))]; $drops[] = ItemItem::get(ItemItem::LEATHER, 0, mt_rand(0, 2 + $lootingL)); return $drops; } } return []; } } attackingTick > 0){ $this->attackingTick--; } if(!$this->isAlive() and $this->hasSpawned){ ++$this->deadTicks; if($this->deadTicks >= 20){ $this->despawnFromAll(); } return true; } if($this->isAlive()){ $this->motionY -= $this->gravity; $this->move($this->motionX, $this->motionY, $this->motionZ); $friction = 1 - $this->drag; if($this->onGround and (abs($this->motionX) > 0.00001 or abs($this->motionZ) > 0.00001)){ $friction = $this->getLevel()->getBlock($this->temporalVector->setComponents((int) floor($this->x), (int) floor($this->y - 1), (int) floor($this->z) - 1))->getFrictionFactor() * $friction; } $this->motionX *= $friction; $this->motionY *= 1 - $this->drag; $this->motionZ *= $friction; if($this->onGround){ $this->motionY *= -0.5; } $this->updateMovement(); } } parent::entityBaseTick(); return parent::onUpdate($tick); } /** * @param int $distance * * @return bool */ public function willMove($distance = 36){ foreach($this->getViewers() as $viewer){ if($this->distance($viewer->getLocation()) <= $distance) return true; } return false; } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ parent::attack($damage, $source); if(!$source->isCancelled() and $source->getCause() == EntityDamageEvent::CAUSE_ENTITY_ATTACK){ $this->attackingTick = 20; } } /** * @param Level $level * @param Vector3 $v3 * @param bool $hate * @param bool $reason * * @return bool|float|string * 判断某坐标是否可以行走 * 并给出原因 */ public function ifjump(Level $level, Vector3 $v3, $hate = false, $reason = false){ //boybook Y轴算法核心函数 $x = floor($v3->getX()); $y = floor($v3->getY()); $z = floor($v3->getZ()); //echo ($y." "); if($this->whatBlock($level, new Vector3($x, $y, $z)) == "air"){ //echo "前方空气 "; if($this->whatBlock($level, new Vector3($x, $y - 1, $z)) == "block" or new Vector3($x, $y - 1, $z) == "climb"){ //方块 //echo "考虑向前 "; if($this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "block" or $this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "half" or $this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "high"){ //上方一格被堵住了 //echo "上方卡住 \n"; if($reason) return 'up!'; return false; //上方卡住 }else{ //echo "GO向前走 \n"; if($reason) return 'GO'; return $y; //向前走 } }elseif($this->whatBlock($level, new Vector3($x, $y - 1, $z)) == "water"){ //水 //echo "下水游泳 \n"; if($reason) return 'swim'; return $y - 1; //降低一格向前走(下水游泳) }elseif($this->whatBlock($level, new Vector3($x, $y - 1, $z)) == "half"){ //半砖 //echo "下到半砖 \n"; if($reason) return 'half'; return $y - 0.5; //向下跳0.5格 }elseif($this->whatBlock($level, new Vector3($x, $y - 1, $z)) == "lava"){ //岩浆 //echo "前方岩浆 \n"; if($reason) return 'lava'; return false; //前方岩浆 }elseif($this->whatBlock($level, new Vector3($x, $y - 1, $z)) == "air"){ //空气 //echo "考虑向下跳 "; if($this->whatBlock($level, new Vector3($x, $y - 2, $z)) == "block"){ //echo "GO向下跳 \n"; if($reason) return 'down'; return $y - 1; //向下跳 }else{ //前方悬崖 //echo "前方悬崖 \n"; if($reason) return 'fall'; if($hate === false){ return false; }else{ return $y - 1; //向下跳 } } } }elseif($this->whatBlock($level, new Vector3($x, $y, $z)) == "water"){ //水 //echo "正在水中"; if($this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "water"){ //上面还是水 //echo "向上游 \n"; if($reason) return 'inwater'; return $y + 1; //向上游,防溺水 }elseif($this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "block" or $this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "half"){ //上方一格被堵住了 if($this->whatBlock($level, new Vector3($x, $y - 1, $z)) == "block" or $this->whatBlock($level, new Vector3($x, $y - 1, $z)) == "half"){ //下方一格被也堵住了 //echo "上下都被卡住 \n"; if($reason) return 'up!_down!'; return false; //上下都被卡住 }else{ //echo "向下游 \n"; if($reason) return 'up!'; return $y - 1; //向下游,防卡住 } }else{ //echo "游泳ing... \n"; if($reason) return 'swim...'; return $y; //向前游 } }elseif($this->whatBlock($level, new Vector3($x, $y, $z)) == "half"){ //半砖 //echo "前方半砖 \n"; if($this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "block" or $this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "half" or $this->whatBlock($level, new Vector3($x, $y + 1, $z)) == "high"){ //上方一格被堵住了 //return false; //上方卡住 }else{ if($reason) return 'halfGO'; return $y + 0.5; } }elseif($this->whatBlock($level, new Vector3($x, $y, $z)) == "lava"){ //岩浆 //echo "前方岩浆 \n"; if($reason) return 'lava'; return false; }elseif($this->whatBlock($level, new Vector3($x, $y, $z)) == "high"){ //1.5格高方块 //echo "前方栅栏 \n"; if($reason) return 'high'; return false; }elseif($this->whatBlock($level, new Vector3($x, $y, $z)) == "climb"){ //梯子 //echo "前方梯子 \n"; //return $y; if($reason) return 'climb'; if($hate){ return $y + 0.7; }else{ return $y + 0.5; } }else{ //考虑向上 //echo "考虑向上 "; if($this->whatBlock($level, new Vector3($x, $y + 1, $z)) != "air"){ //前方是面墙 //echo "前方是墙 \n"; if($reason) return 'wall'; return false; }else{ if($this->whatBlock($level, new Vector3($x, $y + 2, $z)) == "block" or $this->whatBlock($level, new Vector3($x, $y + 2, $z)) == "half" or $this->whatBlock($level, new Vector3($x, $y + 2, $z)) == "high"){ //上方两格被堵住了 //echo "2格处被堵 \n"; if($reason) return 'up2!'; return false; }else{ //echo "GO向上跳 \n"; if($reason) return 'upGO'; return $y + 1; //向上跳 } } } return false; } /** * @param Level $level * @param $v3 * * @return string */ public function whatBlock(Level $level, $v3){ //boybook的y轴判断法 核心 什么方块? $id = $level->getBlockIdAt($v3->x, $v3->y, $v3->z); $damage = $level->getBlockDataAt($v3->x, $v3->y, $v3->z); switch($id){ case 0: case 6: case 27: case 30: case 31: case 37: case 38: case 39: case 40: case 50: case 51: case 63: case 66: case 68: case 78: case 111: case 141: case 142: case 171: case 175: case 244: case 323: //透明方块 return "air"; break; case 8: case 9: //水 return "water"; break; case 10: case 11: //岩浆 return "lava"; break; case 44: case 158: //半砖 if($damage >= 8){ return "block"; }else{ return "half"; } break; case 64: //门 //var_dump($damage." "); //TODO 不知如何判断门是否开启,因为以下条件永远满足 if(($damage & 0x08) === 0x08){ return "air"; }else{ return "block"; } break; case 85: case 107: case 139: //1.5格高的无法跳跃物 return "high"; break; case 65: case 106: //可攀爬物 return "climb"; break; default: //普通方块 return "block"; break; } } /** * @param $mx * @param $mz * * @return float|int * 获取yaw角度 */ public function getMyYaw($mx, $mz){ //根据motion计算转向角度 //转向计算 if($mz == 0){ //斜率不存在 if($mx < 0){ $yaw = -90; }else{ $yaw = 90; } }else{ //存在斜率 if($mx >= 0 and $mz > 0){ //第一象限 $atan = atan($mx / $mz); $yaw = rad2deg($atan); }elseif($mx >= 0 and $mz < 0){ //第二象限 $atan = atan($mx / abs($mz)); $yaw = 180 - rad2deg($atan); }elseif($mx < 0 and $mz < 0){ //第三象限 $atan = atan($mx / $mz); $yaw = -(180 - rad2deg($atan)); }elseif($mx < 0 and $mz > 0){ //第四象限 $atan = atan(abs($mx) / $mz); $yaw = -(rad2deg($atan)); }else{ $yaw = 0; } } $yaw = -$yaw; return $yaw; } /** * @param Vector3 $from * @param Vector3 $to * * @return float|int * 获取pitch角度 */ public function getMyPitch(Vector3 $from, Vector3 $to){ $distance = $from->distance($to); $height = $to->y - $from->y; if($height > 0){ return -rad2deg(asin($height / $distance)); }elseif($height < 0){ return rad2deg(asin(-$height / $distance)); }else{ return 0; } } }namedtag->powered)){ $this->setPowered(false); } $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_POWERED, $this->isPowered()); } /** * @param bool $powered * @param Lightning|null $lightning */ public function setPowered(bool $powered, Lightning $lightning = null){ if($lightning != null){ $powered = true; $cause = CreeperPowerEvent::CAUSE_LIGHTNING; }else $cause = $powered ? CreeperPowerEvent::CAUSE_SET_ON : CreeperPowerEvent::CAUSE_SET_OFF; $this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new CreeperPowerEvent($this, $lightning, $cause)); if(!$ev->isCancelled()){ $this->namedtag->powered = new ByteTag("powered", $powered ? 1 : 0); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_POWERED, $powered); } } /** * @return bool */ public function isPowered() : bool{ return (bool) $this->namedtag["powered"]; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Creeper::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }setMaxHealth(20); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Donkey::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::LEATHER, 0, mt_rand(1, 2)) ]; return $drops; } } id = $id; $this->name = $name; $this->bad = (bool) $isBad; $this->setColor($r, $g, $b); } /** * @return string */ public function getName() : string{ return $this->name; } /** * @return int */ public function getId(){ return $this->id; } /** * @param $ticks * * @return $this */ public function setDuration($ticks){ $this->duration = (($ticks > self::MAX_DURATION) ? self::MAX_DURATION : $ticks); return $this; } public function getDuration(){ return $this->duration; } /** * @return bool */ public function isVisible(){ return $this->show; } /** * @param $bool * * @return $this */ public function setVisible($bool){ $this->show = (bool) $bool; return $this; } /** * @return int */ public function getAmplifier(){ return $this->amplifier; } /** * @param int $amplifier * * @return $this */ public function setAmplifier(int $amplifier){ $this->amplifier = $amplifier & 0xff; return $this; } /** * @return bool */ public function isAmbient(){ return $this->ambient; } /** * @param bool $ambient * * @return $this */ public function setAmbient($ambient = true){ $this->ambient = (bool) $ambient; return $this; } /** * @return bool */ public function isBad(){ return $this->bad; } /** * @return bool */ public function canTick(){ if($this->amplifier < 0) $this->amplifier = 0; switch($this->id){ case Effect::POISON: if(($interval = (25 >> $this->amplifier)) > 0){ return ($this->duration % $interval) === 0; } return true; case Effect::WITHER: if(($interval = (50 >> $this->amplifier)) > 0){ return ($this->duration % $interval) === 0; } return true; case Effect::REGENERATION: if(($interval = (40 >> $this->amplifier)) > 0){ return ($this->duration % $interval) === 0; } return true; case Effect::HUNGER: if($this->amplifier < 0){ // prevents hacking with amplifier -1 return false; } if(($interval = 20) > 0){ return ($this->duration % $interval) === 0; } return true; case Effect::HEALING: case Effect::HARMING: return true; case Effect::SATURATION: if(($interval = (20 >> $this->amplifier)) > 0){ return ($this->duration % $interval) === 0; } return true; } return false; } /** * @param Entity $entity */ public function applyEffect(Entity $entity){ switch($this->id){ case Effect::POISON: if($entity->getHealth() > 1){ $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, 1); $entity->attack($ev->getFinalDamage(), $ev); } break; case Effect::WITHER: $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, 1); $entity->attack($ev->getFinalDamage(), $ev); break; case Effect::REGENERATION: if($entity->getHealth() < $entity->getMaxHealth()){ $ev = new EntityRegainHealthEvent($entity, 1, EntityRegainHealthEvent::CAUSE_MAGIC); $entity->heal($ev->getAmount(), $ev); } break; case Effect::HUNGER: if($entity instanceof Human){ $entity->exhaust(0.5 * $this->amplifier, PlayerExhaustEvent::CAUSE_POTION); } break; case Effect::HEALING: $level = $this->amplifier + 1; if(($entity->getHealth() + 4 * $level) <= $entity->getMaxHealth()){ $ev = new EntityRegainHealthEvent($entity, 4 * $level, EntityRegainHealthEvent::CAUSE_MAGIC); $entity->heal($ev->getAmount(), $ev); }else{ $ev = new EntityRegainHealthEvent($entity, $entity->getMaxHealth() - $entity->getHealth(), EntityRegainHealthEvent::CAUSE_MAGIC); $entity->heal($ev->getAmount(), $ev); } break; case Effect::HARMING: $level = $this->amplifier + 1; if(($entity->getHealth() - 6 * $level) >= 0){ $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, 6 * $level); $entity->attack($ev->getFinalDamage(), $ev); }else{ $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_MAGIC, $entity->getHealth()); $entity->attack($ev->getFinalDamage(), $ev); } break; case Effect::SATURATION: if($entity instanceof Player){ if($entity->getServer()->foodEnabled){ $entity->setFood($entity->getFood() + 1); } } break; } } /** * @return array */ public function getColor(){ return [$this->color >> 16, ($this->color >> 8) & 0xff, $this->color & 0xff]; } /** * @param $r * @param $g * @param $b */ public function setColor($r, $g, $b){ $this->color = (($r & 0xff) << 16) + (($g & 0xff) << 8) + ($b & 0xff); } /** * @param Entity $entity * @param bool $modify * @param Effect|null $oldEffect */ public function add(Entity $entity, $modify = false, Effect $oldEffect = null){ if($entity instanceof Player){ $pk = new MobEffectPacket(); $pk->eid = $entity->getId(); $pk->effectId = $this->getId(); $pk->amplifier = $this->getAmplifier(); $pk->particles = $this->isVisible(); $pk->duration = $this->getDuration(); if($modify){ $pk->eventId = MobEffectPacket::EVENT_MODIFY; }else{ $pk->eventId = MobEffectPacket::EVENT_ADD; } $entity->dataPacket($pk); if($this->id === Effect::SPEED){ $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); if($modify and $oldEffect !== null){ $speed = $attr->getValue() / (1 + 0.2 * ($oldEffect->getAmplifier() + 1)); }else{ $speed = $attr->getValue(); } $speed *= (1 + 0.2 * ($this->amplifier + 1)); $attr->setValue($speed); }elseif($this->id === Effect::SLOWNESS){ $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); if($modify and $oldEffect !== null){ $speed = $attr->getValue() / (1 - 0.15 * ($oldEffect->getAmplifier() + 1)); }else{ $speed = $attr->getValue(); } $speed *= (1 - (0.15 * $this->amplifier + 1)); $attr->setValue($speed); } } if($this->id === Effect::INVISIBILITY){ $entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, true); $entity->setNameTagVisible(false); } } /** * @param Entity $entity */ public function remove(Entity $entity){ if($entity instanceof Player){ $pk = new MobEffectPacket(); $pk->eid = $entity->getId(); $pk->eventId = MobEffectPacket::EVENT_REMOVE; $pk->effectId = $this->getId(); $entity->dataPacket($pk); if($this->id === Effect::SPEED){ $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); $attr->setValue($attr->getValue() / (1 + 0.2 * ($this->amplifier + 1))); }elseif($this->id === Effect::SLOWNESS){ $attr = $entity->getAttributeMap()->getAttribute(Attribute::MOVEMENT_SPEED); $attr->setValue($attr->getValue() / (1 - 0.15 * ($this->amplifier + 1))); } } if($this->id === Effect::INVISIBILITY){ $entity->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_INVISIBLE, false); $entity->setNameTagVisible(true); } } } closed){ return false; } $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); if($this->age > 1200 or $this->isCollided){ $this->kill(); $hasUpdate = true; //Chance to spawn chicken } $this->timings->stopTiming(); return $hasUpdate; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = Egg::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } setMaxHealth(80); $this->setDataFlag(Entity::DATA_FLAGS, Entity::DATA_FLAG_ELDER, true); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = ElderGuardian::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::PRISMARINE_CRYSTALS, 0, mt_rand(0, 1)) ]; $drops[] = ItemItem::get(ItemItem::PRISMARINE_SHARD, 0, mt_rand(0, 2)); return $drops; } } setMaxHealth(200); parent::initEntity(); } /** * @return string */ public function getName() : string{ return "Ender Dragon"; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }hasTeleportedShooter){ $this->hasTeleportedShooter = true; if($this->shootingEntity instanceof Player and $this->y > 0){ $this->getLevel()->addSound(new EndermanTeleportSound($this->getPosition()), array($this->shootingEntity)); $this->shootingEntity->teleport($this->getPosition()); } $this->kill(); } } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); if($this->age > 1200 or $this->isCollided){ $this->teleportShooter(); $hasUpdate = true; } $this->timings->stopTiming(); return $hasUpdate; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = EnderPearl::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } eid = $this->getId(); $pk->type = Enderman::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }setMaxHealth(8); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Endermite::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } [self::DATA_TYPE_LONG, 0], self::DATA_AIR => [self::DATA_TYPE_SHORT, 400], self::DATA_MAX_AIR => [self::DATA_TYPE_SHORT, 400], self::DATA_NAMETAG => [self::DATA_TYPE_STRING, ""], self::DATA_LEAD_HOLDER_EID => [self::DATA_TYPE_LONG, -1], self::DATA_SCALE => [self::DATA_TYPE_FLOAT, 1], ]; public $passenger = null; public $vehicle = null; /** @var Chunk */ public $chunk; protected $lastDamageCause = null; /** @var Block[] */ private $blocksAround = []; public $lastX = null; public $lastY = null; public $lastZ = null; public $motionX; public $motionY; public $motionZ; /** @var Vector3 */ public $temporalVector; public $lastMotionX; public $lastMotionY; public $lastMotionZ; public $lastYaw; public $lastPitch; /** @var AxisAlignedBB */ public $boundingBox; public $onGround; public $inBlock = false; public $positionChanged; public $motionChanged; public $deadTicks = 0; protected $age = 0; public $height; public $eyeHeight = null; public $width; public $length; /** @var int */ private $health = 20; private $maxHealth = 20; protected $ySize = 0; protected $stepHeight = 0; public $keepMovement = false; public $fallDistance = 0; public $ticksLived = 0; public $lastUpdate; public $maxFireTicks; public $fireTicks = 0; public $namedtag; public $canCollide = true; protected $isStatic = false; public $isCollided = false; public $isCollidedHorizontally = false; public $isCollidedVertically = false; public $noDamageTicks; protected $justCreated; private $invulnerable; /** @var AttributeMap */ protected $attributeMap; protected $gravity; protected $drag; /** @var Server */ protected $server; public $closed = false; /** @var \pocketmine\event\TimingsHandler */ protected $timings; protected $isPlayer = false; /** @var Entity */ protected $linkedEntity = null; /** 0 no linked 1 linked other 2 be linked */ protected $linkedType = null; protected $riding = null; /** @var PressurePlate */ protected $activatedPressurePlates = []; public $dropExp = [0, 0]; /** * Entity constructor. * * @param Level $level * @param CompoundTag $nbt */ public function __construct(Level $level, CompoundTag $nbt){ $this->timings = Timings::getEntityTimings($this); $this->isPlayer = $this instanceof Player; $this->temporalVector = new Vector3(); if($this->eyeHeight === null){ $this->eyeHeight = $this->height / 2 + 0.1; } $this->id = Entity::$entityCount++; $this->justCreated = true; $this->namedtag = $nbt; $this->chunk = $level->getChunk($this->namedtag["Pos"][0] >> 4, $this->namedtag["Pos"][2] >> 4); assert($this->chunk !== null); $this->setLevel($level); $this->server = $level->getServer(); $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); $this->setPositionAndRotation( $this->temporalVector->setComponents( $this->namedtag["Pos"][0], $this->namedtag["Pos"][1], $this->namedtag["Pos"][2] ), $this->namedtag->Rotation[0], $this->namedtag->Rotation[1] ); $this->setMotion($this->temporalVector->setComponents($this->namedtag["Motion"][0], $this->namedtag["Motion"][1], $this->namedtag["Motion"][2])); assert(!is_nan($this->x) and !is_infinite($this->x) and !is_nan($this->y) and !is_infinite($this->y) and !is_nan($this->z) and !is_infinite($this->z)); if(!isset($this->namedtag->FallDistance)){ $this->namedtag->FallDistance = new FloatTag("FallDistance", 0); } $this->fallDistance = $this->namedtag["FallDistance"]; if(!isset($this->namedtag->Fire)){ $this->namedtag->Fire = new ShortTag("Fire", 0); } $this->fireTicks = $this->namedtag["Fire"]; if(!isset($this->namedtag->Air)){ $this->namedtag->Air = new ShortTag("Air", 300); } $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, $this->namedtag["Air"]); if(!isset($this->namedtag->OnGround)){ $this->namedtag->OnGround = new ByteTag("OnGround", 0); } $this->onGround = $this->namedtag["OnGround"] > 0 ? true : false; if(!isset($this->namedtag->Invulnerable)){ $this->namedtag->Invulnerable = new ByteTag("Invulnerable", 0); } $this->invulnerable = $this->namedtag["Invulnerable"] > 0 ? true : false; $this->attributeMap = new AttributeMap(); $this->chunk->addEntity($this); $this->level->addEntity($this); $this->initEntity(); $this->lastUpdate = $this->server->getTick(); $this->server->getPluginManager()->callEvent(new EntitySpawnEvent($this)); $this->scheduleUpdate(); } //add original function (use create AI etc) /** * @return mixed */ public function getHeight(){ return $this->height; } /** * @return mixed */ public function getWidth(){ return $this->width; } /** * @return mixed */ public function getLength(){ return $this->length; } //add original function (set scale etc) /** * @param $scale */ public function setScale($scale){ $this->setDataProperty(self::DATA_SCALE, self::DATA_TYPE_FLOAT, $scale); } /** * @return mixed */ public function getScale(){ return $this->getDataProperty(self::DATA_SCALE, self::DATA_TYPE_FLOAT); } /** * @return int */ public function getDropExpMin() : int{ return $this->dropExp[0]; } /** * @return int */ public function getDropExpMax() : int{ return $this->dropExp[1]; } /** * @return string */ public function getNameTag(){ return $this->getDataProperty(self::DATA_NAMETAG); } /** * @return bool */ public function isNameTagVisible(){ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_CAN_SHOW_NAMETAG); } /** * @return bool */ public function isNameTagAlwaysVisible(){ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ALWAYS_SHOW_NAMETAG); } /** * @param string $name */ public function setNameTag($name){ $this->setDataProperty(self::DATA_NAMETAG, self::DATA_TYPE_STRING, $name); } /** * @param bool $value */ public function setNameTagVisible($value = true){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_CAN_SHOW_NAMETAG, $value); } /** * @param bool $value */ public function setNameTagAlwaysVisible($value = true){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ALWAYS_SHOW_NAMETAG, $value); } /** * @return bool */ public function isSneaking(){ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_SNEAKING); } /** * @param bool $value */ public function setSneaking($value = true){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_SNEAKING, (bool) $value); } /** * @return bool */ public function isSprinting(){ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_SPRINTING); } /** * @param bool $value */ public function setSprinting($value = true){ if($value !== $this->isSprinting()){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_SPRINTING, (bool) $value); $attr = $this->attributeMap->getAttribute(Attribute::MOVEMENT_SPEED); $attr->setValue($value ? ($attr->getValue() * 1.3) : ($attr->getValue() / 1.3)); } } /** * @return bool */ public function isGliding(){ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_IDLING); } /** * @param bool $value */ public function setGliding($value = true){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_GLIDING, (bool) $value); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_IDLING, (bool) $value); } /** * @return bool */ public function isImmobile() : bool{ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_IMMOBILE); } /** * @param bool $value */ public function setImmobile($value = true){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_IMMOBILE, $value); } /** * Returns whether the entity is able to climb blocks such as ladders or vines. * * @return bool */ public function canClimb() : bool{ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_CAN_CLIMB); } /** * Sets whether the entity is able to climb climbable blocks. * * @param bool $value */ public function setCanClimb(bool $value){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_CAN_CLIMB, $value); } /** * Returns whether this entity is climbing a block. By default this is only true if the entity is climbing a ladder or vine or similar block. * * @return bool */ public function canClimbWalls() : bool{ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_WALLCLIMBING); } /** * Sets whether the entity is climbing a block. If true, the entity can climb anything. * * @param bool $value */ public function setCanClimbWalls(bool $value = true){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_WALLCLIMBING, $value); } /** * Returns the entity ID of the owning entity, or null if the entity doesn't have an owner. * @return int|string|null */ public function getOwningEntityId(){ return $this->getDataProperty(self::DATA_OWNER_EID); } /** * Returns the owning entity, or null if the entity was not found. * @return Entity|null */ public function getOwningEntity(){ $eid = $this->getOwningEntityId(); if($eid !== null){ return $this->server->findEntity($eid, $this->level); } return null; } /** * Sets the owner of the entity. * * @param Entity $owner * * @throws \InvalidArgumentException if the supplied entity is not valid */ public function setOwningEntity(Entity $owner){ if($owner->closed){ throw new \InvalidArgumentException("Supplied owning entity is garbage and cannot be used"); return false; } $this->setDataProperty(self::DATA_OWNER_EID, self::DATA_TYPE_LONG, $owner->getId()); return true; } /** * @return Effect[] */ public function getEffects(){ return $this->effects; } public function removeAllEffects(){ foreach($this->effects as $effect){ $this->removeEffect($effect->getId()); } } /** * @param $effectId * * @return bool */ public function removeEffect($effectId){ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityEffectRemoveEvent($this, $effectId)); if($ev->isCancelled()){ return false; } if(isset($this->effects[$effectId])){ $effect = $this->effects[$effectId]; unset($this->effects[$effectId]); $effect->remove($this); if($effectId === Effect::ABSORPTION and $this instanceof Human){ $this->setAbsorption(0); } $this->recalculateEffectColor(); return true; } return false; } /** * @param $effectId * * @return null|Effect */ public function getEffect($effectId){ return isset($this->effects[$effectId]) ? $this->effects[$effectId] : null; } /** * @param $effectId * * @return bool */ public function hasEffect($effectId){ return isset($this->effects[$effectId]); } /** * @param Effect $effect * * @return bool */ public function addEffect(Effect $effect){ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityEffectAddEvent($this, $effect)); if($ev->isCancelled()){ return false; } if($effect->getId() === Effect::HEALTH_BOOST){ $this->setHealth($this->getHealth() + 4 * ($effect->getAmplifier() + 1)); } if($effect->getId() === Effect::ABSORPTION and $this instanceof Human){ $this->setAbsorption(4 * ($effect->getAmplifier() + 1)); } if(isset($this->effects[$effect->getId()])){ $oldEffect = $this->effects[$effect->getId()]; if(($effect->getAmplifier() <= ($oldEffect->getAmplifier())) and $effect->getDuration() < $oldEffect->getDuration()){ return false; } $effect->add($this, true, $oldEffect); }else{ $effect->add($this, false); } $this->effects[$effect->getId()] = $effect; $this->recalculateEffectColor(); return true; } protected function recalculateEffectColor(){ //TODO: add transparency values $color = [0, 0, 0]; //RGB $count = 0; $ambient = true; foreach($this->effects as $effect){ if($effect->isVisible()){ $c = $effect->getColor(); $color[0] += $c[0] * ($effect->getAmplifier() + 1); $color[1] += $c[1] * ($effect->getAmplifier() + 1); $color[2] += $c[2] * ($effect->getAmplifier() + 1); $count += $effect->getAmplifier() + 1; if(!$effect->isAmbient()){ $ambient = false; } } } if($count > 0){ $r = ($color[0] / $count) & 0xff; $g = ($color[1] / $count) & 0xff; $b = ($color[2] / $count) & 0xff; $this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0xff000000 | ($r << 16) | ($g << 8) | $b); $this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, $ambient ? 1 : 0); }else{ $this->setDataProperty(Entity::DATA_POTION_COLOR, Entity::DATA_TYPE_INT, 0); $this->setDataProperty(Entity::DATA_POTION_AMBIENT, Entity::DATA_TYPE_BYTE, 0); } } /** * @param int|string $type * @param Level $level * @param CompoundTag $nbt * @param $args * * @return Entity|Projectile */ public static function createEntity($type, Level $level, CompoundTag $nbt, ...$args){ if(isset(self::$knownEntities[$type])){ $class = self::$knownEntities[$type]; return new $class($level, $nbt, ...$args); } return null; } /** * @param $className * @param bool $force * * @return bool */ public static function registerEntity($className, $force = false){ $class = new \ReflectionClass($className); if(is_a($className, Entity::class, true) and !$class->isAbstract()){ if($className::NETWORK_ID !== -1){ self::$knownEntities[$className::NETWORK_ID] = $className; }elseif(!$force){ return false; } self::$knownEntities[$class->getShortName()] = $className; self::$shortNames[$className] = $class->getShortName(); return true; } return false; } /** * Returns the short save name * * @return string */ public function getSaveId(){ return self::$shortNames[static::class]; } public function saveNBT(){ if(!($this instanceof Player)){ $this->namedtag->id = new StringTag("id", $this->getSaveId()); if($this->getNameTag() !== ""){ $this->namedtag->CustomName = new StringTag("CustomName", $this->getNameTag()); $this->namedtag->CustomNameVisible = new StringTag("CustomNameVisible", $this->isNameTagVisible()); $this->namedtag->CustomNameAlwaysVisible = new StringTag("CustomNameAlwaysVisible", $this->isNameTagAlwaysVisible()); }else{ unset($this->namedtag->CustomName); unset($this->namedtag->CustomNameVisible); unset($this->namedtag->CustomNameAlwaysVisible); } } $this->namedtag->Pos = new ListTag("Pos", [ new DoubleTag(0, $this->x), new DoubleTag(1, $this->y), new DoubleTag(2, $this->z) ]); $this->namedtag->Motion = new ListTag("Motion", [ new DoubleTag(0, $this->motionX), new DoubleTag(1, $this->motionY), new DoubleTag(2, $this->motionZ) ]); $this->namedtag->Rotation = new ListTag("Rotation", [ new FloatTag(0, $this->yaw), new FloatTag(1, $this->pitch) ]); $this->namedtag->FallDistance = new FloatTag("FallDistance", $this->fallDistance); $this->namedtag->Fire = new ShortTag("Fire", $this->fireTicks); $this->namedtag->Air = new ShortTag("Air", $this->getDataProperty(self::DATA_AIR)); $this->namedtag->OnGround = new ByteTag("OnGround", $this->onGround == true ? 1 : 0); $this->namedtag->Invulnerable = new ByteTag("Invulnerable", $this->invulnerable == true ? 1 : 0); if(count($this->effects) > 0){ $effects = []; foreach($this->effects as $effect){ $effects[$effect->getId()] = new CompoundTag($effect->getId(), [ "Id" => new ByteTag("Id", $effect->getId()), "Amplifier" => new ByteTag("Amplifier", $effect->getAmplifier()), "Duration" => new IntTag("Duration", $effect->getDuration()), "Ambient" => new ByteTag("Ambient", 0), "ShowParticles" => new ByteTag("ShowParticles", $effect->isVisible() ? 1 : 0) ]); } $this->namedtag->ActiveEffects = new ListTag("ActiveEffects", $effects); }else{ unset($this->namedtag->ActiveEffects); } } protected function initEntity(){ if(!($this->namedtag instanceof CompoundTag)){ throw new \InvalidArgumentException("Expecting CompoundTag, received " . get_class($this->namedtag)); } if(isset($this->namedtag->CustomName)){ $this->setNameTag($this->namedtag["CustomName"]); if(isset($this->namedtag->CustomNameVisible)){ $this->setNameTagVisible($this->namedtag["CustomNameVisible"] > 0); } if(isset($this->namedtag->CustomNameAlwaysVisible)){ $this->setNameTagAlwaysVisible($this->namedtag["CustomNameAlwaysVisible"] > 0); } } $this->scheduleUpdate(); $this->addAttributes(); if(isset($this->namedtag->ActiveEffects)){ foreach($this->namedtag->ActiveEffects->getValue() as $e){ $amplifier = $e["Amplifier"] & 0xff; //0-255 only $effect = Effect::getEffect($e["Id"]); if($effect === null){ continue; } $effect->setAmplifier($amplifier)->setDuration($e["Duration"])->setVisible($e["ShowParticles"] > 0); $this->addEffect($effect); } } } protected function addAttributes(){ } /** * @return Player[] */ public function getViewers(){ return $this->hasSpawned; } /** * @param Player $player */ public function spawnTo(Player $player){ if(!isset($this->hasSpawned[$player->getLoaderId()]) and isset($player->usedChunks[Level::chunkHash($this->chunk->getX(), $this->chunk->getZ())])){ $this->hasSpawned[$player->getLoaderId()] = $player; } } /** * @param Player $player */ public function sendPotionEffects(Player $player){ foreach($this->effects as $effect){ $pk = new MobEffectPacket(); $pk->eid = $this->id; $pk->effectId = $effect->getId(); $pk->amplifier = $effect->getAmplifier(); $pk->particles = $effect->isVisible(); $pk->duration = $effect->getDuration(); $pk->eventId = MobEffectPacket::EVENT_ADD; $player->dataPacket($pk); } } /** * @param Player[]|Player $player * @param array $data Properly formatted entity data, defaults to everything */ public function sendData($player, array $data = null){ if(!is_array($player)){ $player = [$player]; } $pk = new SetEntityDataPacket(); $pk->eid = $this->getId(); $pk->metadata = $data === null ? $this->dataProperties : $data; foreach($player as $p){ if($p === $this){ continue; } $p->dataPacket(clone $pk); } if($this instanceof Player){ $this->dataPacket($pk); } } /** * @param Player $player * @param bool $send */ public function despawnFrom(Player $player, bool $send = true){ if(isset($this->hasSpawned[$player->getLoaderId()])){ if($send){ $pk = new RemoveEntityPacket(); $pk->eid = $this->id; $player->dataPacket($pk); } unset($this->hasSpawned[$player->getLoaderId()]); } } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool */ public function attack($damage, EntityDamageEvent $source){ if($this->hasEffect(Effect::FIRE_RESISTANCE) and ($source->getCause() === EntityDamageEvent::CAUSE_FIRE or $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK or $source->getCause() === EntityDamageEvent::CAUSE_LAVA) ){ $source->setCancelled(); } $this->server->getPluginManager()->callEvent($source); if($source->isCancelled()){ return false; } $this->setLastDamageCause($source); if($this instanceof Human){ $damage = round($source->getFinalDamage()); if($this->getAbsorption() > 0){ $absorption = $this->getAbsorption() - $damage; $this->setAbsorption($absorption <= 0 ? 0 : $absorption); $this->setHealth($this->getHealth() + $absorption); }else{ $this->setHealth($this->getHealth() - $damage); } }else{ $this->setHealth($this->getHealth() - round($source->getFinalDamage())); } return true; } /** * @param float $amount * @param EntityRegainHealthEvent $source * */ public function heal($amount, EntityRegainHealthEvent $source){ $this->server->getPluginManager()->callEvent($source); if($source->isCancelled()){ return; } $this->setHealth($this->getHealth() + $source->getAmount()); } /** * @return int */ public function getHealth(){ return $this->health; } /** * @return bool */ public function isAlive(){ return $this->health > 0; } /** * Sets the health of the Entity. This won't send any update to the players * * @param int $amount */ public function setHealth($amount){ $amount = (int) $amount; if($amount === $this->health){ return; } if($amount <= 0){ if($this->isAlive()){ $this->kill(); } }elseif($amount <= $this->getMaxHealth() or $amount < $this->health){ $this->health = (int) $amount; }else{ $this->health = $this->getMaxHealth(); } } /** * @param EntityDamageEvent $type */ public function setLastDamageCause(EntityDamageEvent $type){ $this->lastDamageCause = $type; } /** * @return EntityDamageEvent|null */ public function getLastDamageCause(){ return $this->lastDamageCause; } /** * @return AttributeMap */ public function getAttributeMap(){ return $this->attributeMap; } /** * @return int */ public function getMaxHealth(){ return $this->maxHealth + ($this->hasEffect(Effect::HEALTH_BOOST) ? 4 * ($this->getEffect(Effect::HEALTH_BOOST)->getAmplifier() + 1) : 0); } /** * @param int $amount */ public function setMaxHealth($amount){ $this->maxHealth = (int) $amount; } /** * @param Entity $entity * * @return bool */ public function canCollideWith(Entity $entity){ return !$this->justCreated and $entity !== $this; } /** * @param $x * @param $y * @param $z * * @return bool */ protected function checkObstruction($x, $y, $z){ $i = Math::floorFloat($x); $j = Math::floorFloat($y); $k = Math::floorFloat($z); $diffX = $x - $i; $diffY = $y - $j; $diffZ = $z - $k; if(Block::$solid[$this->level->getBlockIdAt($i, $j, $k)]){ $flag = !Block::$solid[$this->level->getBlockIdAt($i - 1, $j, $k)]; $flag1 = !Block::$solid[$this->level->getBlockIdAt($i + 1, $j, $k)]; $flag2 = !Block::$solid[$this->level->getBlockIdAt($i, $j - 1, $k)]; $flag3 = !Block::$solid[$this->level->getBlockIdAt($i, $j + 1, $k)]; $flag4 = !Block::$solid[$this->level->getBlockIdAt($i, $j, $k - 1)]; $flag5 = !Block::$solid[$this->level->getBlockIdAt($i, $j, $k + 1)]; $direction = -1; $limit = 9999; if($flag){ $limit = $diffX; $direction = 0; } if($flag1 and 1 - $diffX < $limit){ $limit = 1 - $diffX; $direction = 1; } if($flag2 and $diffY < $limit){ $limit = $diffY; $direction = 2; } if($flag3 and 1 - $diffY < $limit){ $limit = 1 - $diffY; $direction = 3; } if($flag4 and $diffZ < $limit){ $limit = $diffZ; $direction = 4; } if($flag5 and 1 - $diffZ < $limit){ $direction = 5; } $force = lcg_value() * 0.2 + 0.1; if($direction === 0){ $this->motionX = -$force; return true; } if($direction === 1){ $this->motionX = $force; return true; } if($direction === 2){ $this->motionY = -$force; return true; } if($direction === 3){ $this->motionY = $force; return true; } if($direction === 4){ $this->motionZ = -$force; return true; } if($direction === 5){ $this->motionZ = $force; return true; } } return false; } /** * @param int $tickDiff * * @return bool */ public function entityBaseTick($tickDiff = 1){ Timings::$timerEntityBaseTick->startTiming(); //TODO: check vehicles $this->blocksAround = null; $this->justCreated = false; if(!$this->isAlive()){ $this->removeAllEffects(); $this->despawnFromAll(); if(!$this->isPlayer){ $this->close(); } Timings::$timerEntityBaseTick->stopTiming(); return false; } if(count($this->effects) > 0){ foreach($this->effects as $effect){ if($effect->canTick()){ $effect->applyEffect($this); } $effect->setDuration($effect->getDuration() - $tickDiff); if($effect->getDuration() <= 0){ $this->removeEffect($effect->getId()); } } } $hasUpdate = false; $this->checkBlockCollision(); if($this->y <= -16 and $this->isAlive()){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10); $this->attack($ev->getFinalDamage(), $ev); $hasUpdate = true; } if($this->fireTicks > 0){ if($this->isFireProof()){ if($this->fireTicks > 1){ $this->fireTicks = 1; }else{ $this->fireTicks -= 1; } }else{ if(!$this->hasEffect(Effect::FIRE_RESISTANCE) and (($this->fireTicks % 20) === 0 or $tickDiff > 20)){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_FIRE_TICK, 1); $this->attack($ev->getFinalDamage(), $ev); } $this->fireTicks -= $tickDiff; } if($this->fireTicks <= 0 && $this->fireTicks > -10){ $this->extinguish(); }else{ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ONFIRE, true); $hasUpdate = true; } } if($this->noDamageTicks > 0){ $this->noDamageTicks -= $tickDiff; if($this->noDamageTicks < 0){ $this->noDamageTicks = 0; } } $this->age += $tickDiff; $this->ticksLived += $tickDiff; Timings::$timerEntityBaseTick->stopTiming(); return $hasUpdate; } protected function updateMovement(){ $diffPosition = ($this->x - $this->lastX) ** 2 + ($this->y - $this->lastY) ** 2 + ($this->z - $this->lastZ) ** 2; $diffRotation = ($this->yaw - $this->lastYaw) ** 2 + ($this->pitch - $this->lastPitch) ** 2; $diffMotion = ($this->motionX - $this->lastMotionX) ** 2 + ($this->motionY - $this->lastMotionY) ** 2 + ($this->motionZ - $this->lastMotionZ) ** 2; if($diffPosition > 0.04 or $diffRotation > 2.25 and ($diffMotion > 0.0001 and $this->getMotion()->lengthSquared() <= 0.00001)){ //0.2 ** 2, 1.5 ** 2 $this->lastX = $this->x; $this->lastY = $this->y; $this->lastZ = $this->z; $this->lastYaw = $this->yaw; $this->lastPitch = $this->pitch; $this->level->addEntityMovement($this->x >> 4, $this->z >> 4, $this->getId(), $this->x, $this->y + $this->getEyeHeight(), $this->z, $this->yaw, $this->pitch, $this->yaw); } if($diffMotion > 0.0025 or ($diffMotion > 0.0001 and $this->getMotion()->lengthSquared() <= 0.0001)){ //0.05 ** 2 $this->lastMotionX = $this->motionX; $this->lastMotionY = $this->motionY; $this->lastMotionZ = $this->motionZ; $this->level->addEntityMotion($this->chunk->getX(), $this->chunk->getZ(), $this->id, $this->motionX, $this->motionY, $this->motionZ); } } /** * @return Vector3 */ public function getDirectionVector(){ $y = -sin(deg2rad($this->pitch)); $xz = cos(deg2rad($this->pitch)); $x = -$xz * sin(deg2rad($this->yaw)); $z = $xz * cos(deg2rad($this->yaw)); return $this->temporalVector->setComponents($x, $y, $z)->normalize(); } /** * @return Vector2 */ public function getDirectionPlane(){ return (new Vector2(-cos(deg2rad($this->yaw) - M_PI_2), -sin(deg2rad($this->yaw) - M_PI_2)))->normalize(); } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } if(!$this->isAlive()){ ++$this->deadTicks; if($this->deadTicks >= 10){ $this->despawnFromAll(); if(!$this->isPlayer){ $this->close(); } } return $this->deadTicks < 10; } $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0){ return false; } $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = $this->entityBaseTick($tickDiff); $this->updateMovement(); $this->timings->stopTiming(); //if($this->isStatic()) return $hasUpdate; //return !($this instanceof Player); } public final function scheduleUpdate(){ $this->level->updateEntities[$this->id] = $this; } /** * @return bool */ public function isOnFire(){ return $this->fireTicks > 0; } /** * @param $seconds */ public function setOnFire($seconds){ $ticks = $seconds * 20; if($ticks > $this->fireTicks){ $this->fireTicks = $ticks; } } /** * @return bool */ public function isFireProof() : bool{ return false; } /** * @return int|null */ public function getDirection(){ $rotation = ($this->yaw - 90) % 360; if($rotation < 0){ $rotation += 360.0; } if((0 <= $rotation and $rotation < 45) or (315 <= $rotation and $rotation < 360)){ return 2; //North }elseif(45 <= $rotation and $rotation < 135){ return 3; //East }elseif(135 <= $rotation and $rotation < 225){ return 0; //South }elseif(225 <= $rotation and $rotation < 315){ return 1; //West }else{ return null; } } public function extinguish(){ $this->fireTicks = 0; $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_ONFIRE, false); } /** * @return bool */ public function canTriggerWalking(){ return true; } public function resetFallDistance(){ $this->fallDistance = 0; } /** * @param $distanceThisTick * @param $onGround */ protected function updateFallState($distanceThisTick, $onGround){ if($onGround === true){ if($this->fallDistance > 0){ if($this instanceof Living){ $this->fall($this->fallDistance); } $this->resetFallDistance(); } }elseif($distanceThisTick < 0){ $this->fallDistance -= $distanceThisTick; } } /** * @return AxisAlignedBB */ public function getBoundingBox(){ return $this->boundingBox; } /** * @param $fallDistance */ public function fall($fallDistance){ if($this instanceof Player and $this->isSpectator()){ return; } if($fallDistance > 3){ $this->getLevel()->addParticle(new DestroyBlockParticle($this, $this->getLevel()->getBlock($this->floor()->subtract(0, 1, 0)))); } if($this->isInsideOfWater()){ return; } $damage = floor($fallDistance - 3 - ($this->hasEffect(Effect::JUMP) ? $this->getEffect(Effect::JUMP)->getAmplifier() + 1 : 0)); //Get the block directly beneath the player's feet, check if it is a slime block if($this->getLevel()->getBlock($this->floor()->subtract(0, 1, 0)) instanceof SlimeBlock){ $damage = 0; } //TODO Improve if($this instanceof Player){ if($this->getInventory()->getChestplate() instanceof Elytra){ $damage = 0; } } if($damage > 0){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_FALL, $damage); $this->attack($ev->getFinalDamage(), $ev); } } public function handleLavaMovement(){ //TODO } /** * @return float|int|null */ public function getEyeHeight(){ return $this->eyeHeight; } public function moveFlying(){ //TODO } /** * @param Human $entityPlayer */ public function onCollideWithPlayer(Human $entityPlayer){ } /** * @param Level $targetLevel * * @return bool */ protected function switchLevel(Level $targetLevel){ if($this->closed){ return false; } if($this->isValid()){ $this->server->getPluginManager()->callEvent($ev = new EntityLevelChangeEvent($this, $this->level, $targetLevel)); if($ev->isCancelled()){ return false; } $this->level->removeEntity($this); if($this->chunk !== null){ $this->chunk->removeEntity($this); } $this->despawnFromAll(); } $this->setLevel($targetLevel); $this->level->addEntity($this); $this->chunk = null; return true; } /** * @return Position */ public function getPosition(){ return new Position($this->x, $this->y, $this->z, $this->level); } /** * @return Location */ public function getLocation(){ return new Location($this->x, $this->y, $this->z, $this->yaw, $this->pitch, $this->level); } /** * @return bool */ public function isInsideOfPortal(){ $blocks = $this->getBlocksAround(); foreach($blocks as $block){ if($block instanceof Portal){ return true; } } return false; } /** * @return bool */ public function isInsideOfWater(){ $block = $this->level->getBlock($this->temporalVector->setComponents(Math::floorFloat($this->x), Math::floorFloat($y = ($this->y + $this->getEyeHeight())), Math::floorFloat($this->z))); if($block instanceof Water){ $f = ($block->y + 1) - ($block->getFluidHeightPercent() - 0.1111111); return $y < $f; } return false; } /** * @return bool */ public function isInsideOfSolid(){ $block = $this->level->getBlock($this->temporalVector->setComponents(Math::floorFloat($this->x), Math::floorFloat($y = ($this->y + $this->getEyeHeight())), Math::floorFloat($this->z))); $bb = $block->getBoundingBox(); if($bb !== null and $block->isSolid() and !$block->isTransparent() and $bb->intersectsWith($this->getBoundingBox())){ return true; } return false; } /** * @return bool */ public function isInsideOfFire(){ foreach($this->getBlocksAround() as $block){ if($block instanceof Fire){ return true; } } return false; } /** * @param $dx * @param $dy * @param $dz * * @return bool */ public function fastMove($dx, $dy, $dz){ if($dx == 0 and $dz == 0 and $dy == 0){ return true; } Timings::$entityMoveTimer->startTiming(); /*$newBB = $this->boundingBox->getOffsetBoundingBox($dx, $dy, $dz); $list = $this->level->getCollisionCubes($this, $newBB, false); if(count($list) === 0){ $this->boundingBox = $newBB; }*/ $this->x = ($this->boundingBox->minX + $this->boundingBox->maxX) / 2; $this->y = $this->boundingBox->minY - $this->ySize; $this->z = ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2; $this->checkChunks(); if(!$this->onGround or $dy != 0){ $bb = clone $this->boundingBox; $bb->minY -= 0.75; $this->onGround = false; if(!$this->level->getBlock(new Vector3($this->x, $this->y - 1, $this->z))->isTransparent()) $this->onGround = true; /* if(count($this->level->getCollisionBlocks($bb)) > 0){ $this->onGround = true; }*/ } $this->isCollided = $this->onGround; $this->updateFallState($dy, $this->onGround); Timings::$entityMoveTimer->stopTiming(); return true; } /** * @param $dx * @param $dy * @param $dz * * @return bool */ public function move($dx, $dy, $dz){ if($dx == 0 and $dz == 0 and $dy == 0){ return true; } if($this->keepMovement){ $this->boundingBox->offset($dx, $dy, $dz); $this->setPosition($this->temporalVector->setComponents(($this->boundingBox->minX + $this->boundingBox->maxX) / 2, $this->boundingBox->minY, ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2)); $this->onGround = $this->isPlayer ? true : false; return true; }else{ Timings::$entityMoveTimer->startTiming(); $this->ySize *= 0.4; /* if($this->isColliding){ //With cobweb? $this->isColliding = false; $dx *= 0.25; $dy *= 0.05; $dz *= 0.25; $this->motionX = 0; $this->motionY = 0; $this->motionZ = 0; } */ $movX = $dx; $movY = $dy; $movZ = $dz; $axisalignedbb = clone $this->boundingBox; /*$sneakFlag = $this->onGround and $this instanceof Player; if($sneakFlag){ for($mov = 0.05; $dx != 0.0 and count($this->level->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox($dx, -1, 0))) === 0; $movX = $dx){ if($dx < $mov and $dx >= -$mov){ $dx = 0; }elseif($dx > 0){ $dx -= $mov; }else{ $dx += $mov; } } for(; $dz != 0.0 and count($this->level->getCollisionCubes($this, $this->boundingBox->getOffsetBoundingBox(0, -1, $dz))) === 0; $movZ = $dz){ if($dz < $mov and $dz >= -$mov){ $dz = 0; }elseif($dz > 0){ $dz -= $mov; }else{ $dz += $mov; } } //TODO: big messy loop }*/ assert(abs($dx) <= 20 and abs($dy) <= 20 and abs($dz) <= 20, "Movement distance is excessive: dx=$dx, dy=$dy, dz=$dz"); $list = $this->level->getCollisionCubes($this, $this->level->getTickRate() > 1 ? $this->boundingBox->getOffsetBoundingBox($dx, $dy, $dz) : $this->boundingBox->addCoord($dx, $dy, $dz), false); foreach($list as $bb){ $dy = $bb->calculateYOffset($this->boundingBox, $dy); } $this->boundingBox->offset(0, $dy, 0); $fallingFlag = ($this->onGround or ($dy != $movY and $movY < 0)); foreach($list as $bb){ $dx = $bb->calculateXOffset($this->boundingBox, $dx); } $this->boundingBox->offset($dx, 0, 0); foreach($list as $bb){ $dz = $bb->calculateZOffset($this->boundingBox, $dz); } $this->boundingBox->offset(0, 0, $dz); if($this->stepHeight > 0 and $fallingFlag and $this->ySize < 0.05 and ($movX != $dx or $movZ != $dz)){ $cx = $dx; $cy = $dy; $cz = $dz; $dx = $movX; $dy = $this->stepHeight; $dz = $movZ; $axisalignedbb1 = clone $this->boundingBox; $this->boundingBox->setBB($axisalignedbb); $list = $this->level->getCollisionCubes($this, $this->boundingBox->addCoord($dx, $dy, $dz), false); foreach($list as $bb){ $dy = $bb->calculateYOffset($this->boundingBox, $dy); } $this->boundingBox->offset(0, $dy, 0); foreach($list as $bb){ $dx = $bb->calculateXOffset($this->boundingBox, $dx); } $this->boundingBox->offset($dx, 0, 0); foreach($list as $bb){ $dz = $bb->calculateZOffset($this->boundingBox, $dz); } $this->boundingBox->offset(0, 0, $dz); if(($cx ** 2 + $cz ** 2) >= ($dx ** 2 + $dz ** 2)){ $dx = $cx; $dy = $cy; $dz = $cz; $this->boundingBox->setBB($axisalignedbb1); }else{ $this->ySize += 0.5; } } $this->x = ($this->boundingBox->minX + $this->boundingBox->maxX) / 2; $this->y = $this->boundingBox->minY - $this->ySize; $this->z = ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2; $this->checkChunks(); $this->checkGroundState($movX, $movY, $movZ, $dx, $dy, $dz); $this->updateFallState($dy, $this->onGround); if($movX != $dx){ $this->motionX = 0; } if($movY != $dy){ $this->motionY = 0; } if($movZ != $dz){ $this->motionZ = 0; } //TODO: vehicle collision events (first we need to spawn them!) Timings::$entityMoveTimer->stopTiming(); return true; } } /** * @param $movX * @param $movY * @param $movZ * @param $dx * @param $dy * @param $dz */ protected function checkGroundState($movX, $movY, $movZ, $dx, $dy, $dz){ $this->isCollidedVertically = $movY != $dy; $this->isCollidedHorizontally = ($movX != $dx or $movZ != $dz); $this->isCollided = ($this->isCollidedHorizontally or $this->isCollidedVertically); $this->onGround = ($movY != $dy and $movY < 0); } /** * @return array|null|Block[] */ public function getBlocksAround(){ if($this->blocksAround === null){ $minX = Math::floorFloat($this->boundingBox->minX); $minY = Math::floorFloat($this->boundingBox->minY); $minZ = Math::floorFloat($this->boundingBox->minZ); $maxX = Math::ceilFloat($this->boundingBox->maxX); $maxY = Math::ceilFloat($this->boundingBox->maxY); $maxZ = Math::ceilFloat($this->boundingBox->maxZ); $this->blocksAround = []; for($z = $minZ; $z <= $maxZ; ++$z){ for($x = $minX; $x <= $maxX; ++$x){ for($y = $minY; $y <= $maxY; ++$y){ $block = $this->level->getBlock($this->temporalVector->setComponents($x, $y, $z)); if($block->hasEntityCollision()){ $this->blocksAround[Level::blockHash($block->x, $block->y, $block->z)] = $block; } } } } } return $this->blocksAround; } protected function checkBlockCollision(){ $vector = new Vector3(0, 0, 0); foreach($blocksaround = $this->getBlocksAround() as $block){ $block->onEntityCollide($this); $block->addVelocityToEntity($this, $vector); } if($vector->lengthSquared() > 0){ $vector = $vector->normalize(); $d = 0.014; $this->motionX += $vector->x * $d; $this->motionY += $vector->y * $d; $this->motionZ += $vector->z * $d; } } /** * @param Vector3 $pos * @param $yaw * @param $pitch * * @return bool */ public function setPositionAndRotation(Vector3 $pos, $yaw, $pitch){ if($this->setPosition($pos) === true){ $this->setRotation($yaw, $pitch); return true; } return false; } /** * @param $yaw * @param $pitch */ public function setRotation($yaw, $pitch){ $this->yaw = $yaw; $this->pitch = $pitch; $this->scheduleUpdate(); } protected function checkChunks(){ if($this->chunk === null or ($this->chunk->getX() !== ($this->x >> 4) or $this->chunk->getZ() !== ($this->z >> 4))){ if($this->chunk !== null){ $this->chunk->removeEntity($this); } $this->chunk = $this->level->getChunk($this->x >> 4, $this->z >> 4, true); if(!$this->justCreated){ $newChunk = $this->level->getChunkPlayers($this->x >> 4, $this->z >> 4); foreach($this->hasSpawned as $player){ if(!isset($newChunk[$player->getLoaderId()])){ $this->despawnFrom($player); }else{ unset($newChunk[$player->getLoaderId()]); } } foreach($newChunk as $player){ $this->spawnTo($player); } } if($this->chunk === null){ return; } $this->chunk->addEntity($this); } } /** * @param Location $pos * * @return bool */ public function setLocation(Location $pos){ if($this->closed){ return false; } $this->setPositionAndRotation($pos, $pos->yaw, $pos->pitch); return true; } /** * @param Vector3 $pos * * @return bool */ public function setPosition(Vector3 $pos){ if($this->closed){ return false; } if($pos instanceof Position and $pos->level !== null and $pos->level !== $this->level){ if($this->switchLevel($pos->getLevel()) === false){ return false; } } $this->x = $pos->x; $this->y = $pos->y; $this->z = $pos->z; $radius = $this->width / 2; $this->boundingBox->setBounds($pos->x - $radius, $pos->y, $pos->z - $radius, $pos->x + $radius, $pos->y + $this->height, $pos->z + $radius); $this->checkChunks(); return true; } /** * @return Vector3 */ public function getMotion(){ return new Vector3($this->motionX, $this->motionY, $this->motionZ); } /** * @param Vector3 $motion * * @return bool */ public function setMotion(Vector3 $motion){ if(!$this->justCreated){ $this->server->getPluginManager()->callEvent($ev = new EntityMotionEvent($this, $motion)); if($ev->isCancelled()){ return false; } } $this->motionX = $motion->x; $this->motionY = $motion->y; $this->motionZ = $motion->z; if(!$this->justCreated){ $this->updateMovement(); } return true; } /** * @return bool */ public function isOnGround(){ return $this->onGround === true; } public function kill(){ $this->health = 0; $this->removeAllEffects(); $this->scheduleUpdate(); if($this->getLevel()->getServer()->expEnabled){ $exp = mt_rand($this->getDropExpMin(), $this->getDropExpMax()); if($exp > 0) $this->getLevel()->spawnXPOrb($this, $exp); } } /** * @param Vector3|Position|Location $pos * @param float $yaw * @param float $pitch * * @return bool */ public function teleport(Vector3 $pos, $yaw = null, $pitch = null){ if($pos instanceof Location){ $yaw = $yaw === null ? $pos->yaw : $yaw; $pitch = $pitch === null ? $pos->pitch : $pitch; } $from = Position::fromObject($this, $this->level); $to = Position::fromObject($pos, $pos instanceof Position ? $pos->getLevel() : $this->level); $this->server->getPluginManager()->callEvent($ev = new EntityTeleportEvent($this, $from, $to)); if($ev->isCancelled()){ return false; } $this->ySize = 0; $pos = $ev->getTo(); $this->setMotion($this->temporalVector->setComponents(0, 0, 0)); if($this->setPositionAndRotation($pos, $yaw === null ? $this->yaw : $yaw, $pitch === null ? $this->pitch : $pitch) !== false){ $this->resetFallDistance(); $this->onGround = true; $this->lastX = $this->x; $this->lastY = $this->y; $this->lastZ = $this->z; $this->lastYaw = $this->yaw; $this->lastPitch = $this->pitch; $this->updateMovement(); return true; } return false; } /** * @return int */ public function getId(){ return $this->id; } public function respawnToAll(){ foreach($this->hasSpawned as $key => $player){ unset($this->hasSpawned[$key]); $this->spawnTo($player); } } public function spawnToAll(){ if($this->chunk === null or $this->closed){ return; } foreach($this->level->getChunkPlayers($this->chunk->getX(), $this->chunk->getZ()) as $player){ if($player->isOnline()){ $this->spawnTo($player); } } } public function despawnFromAll(){ foreach($this->hasSpawned as $player){ $this->despawnFrom($player); } } public function close(){ if(!$this->closed){ $this->server->getPluginManager()->callEvent(new EntityDespawnEvent($this)); $this->closed = true; $this->removeEffect(Effect::HEALTH_BOOST); $this->despawnFromAll(); if($this->linkedType != 0){ $this->linkedEntity->setLinked(0, $this); } if($this->chunk !== null){ $this->chunk->removeEntity($this); $this->chunk = null; } if($this->getLevel() !== null){ $this->getLevel()->removeEntity($this); //$this->setLevel(null); } $this->namedtag = null; } $this->activatedPressurePlates = []; if($this->attributeMap != null){ $this->attributeMap = null; } } /** * @param int $id * @param int $type * @param mixed $value * * @return bool */ public function setDataProperty($id, $type, $value){ if($this->getDataProperty($id) !== $value){ $this->dataProperties[$id] = [$type, $value]; $this->sendData($this->hasSpawned, [$id => $this->dataProperties[$id]]); return true; } return false; } /** * @param Entity $entity * * @return bool */ public function linkEntity(Entity $entity){ return $this->setLinked(1, $entity); } public function sendLinkedData(){ if($this->linkedEntity instanceof Entity){ $this->setLinked($this->linkedType, $this->linkedEntity); } } /** * @param int $type * @param Entity $entity * * @return bool */ public function setLinked($type = 0, Entity $entity){ if($entity instanceof Boat or $entity instanceof Minecart){ $this->setDataProperty(57, 8, [0, 1, 0]); //This is a fast hack for Boat. TODO: Improve it } if($type != 0 and $entity === null){ return false; } if($entity === $this){ return false; } switch($type){ case 0: if($this->linkedType == 0){ return true; } $this->linkedType = 0; $pk = new SetEntityLinkPacket(); $pk->from = $entity->getId(); $pk->to = $this->getId(); $pk->type = 3; $this->server->broadcastPacket($this->level->getPlayers(), $pk); if($this instanceof Player){ $pk = new SetEntityLinkPacket(); $pk->from = $entity->getId(); $pk->to = 0; $pk->type = 3; $this->dataPacket($pk); } if($this->linkedEntity->getLinkedType()){ $this->linkedEntity->setLinked(0, $this); } $this->linkedEntity = null; return true; case 1: if(!$entity->isAlive()){ return false; } $this->linkedEntity = $entity; $this->linkedType = 1; $entity->linkedEntity = $this; $entity->linkedType = 1; $pk = new SetEntityLinkPacket(); $pk->from = $entity->getId(); $pk->to = $this->getId(); $pk->type = 2; $this->server->broadcastPacket($this->level->getPlayers(), $pk); if($this instanceof Player){ $pk = new SetEntityLinkPacket(); $pk->from = $entity->getId(); $pk->to = 0; $pk->type = 2; $this->dataPacket($pk); } return true; case 2: if(!$entity->isAlive()){ return false; } if($entity->getLinkedEntity() !== $this){ return $entity->linkEntity($this); } $this->linkedEntity = $entity; $this->linkedType = 2; return true; default: return false; } } /** * @return Entity */ public function getLinkedEntity(){ return $this->linkedEntity; } /** * @return null */ public function getLinkedType(){ return $this->linkedType; } /** * @param int $id * * @return mixed */ public function getDataProperty($id){ return isset($this->dataProperties[$id]) ? $this->dataProperties[$id][1] : null; } /** * @param int $id * * @return int */ public function getDataPropertyType($id){ return isset($this->dataProperties[$id]) ? $this->dataProperties[$id][0] : null; } /** * @param $propertyId * @param $id * @param bool $value * @param int $type */ public function setDataFlag($propertyId, $id, $value = true, $type = self::DATA_TYPE_LONG){ if($this->getDataFlag($propertyId, $id) !== $value){ $flags = (int) $this->getDataProperty($propertyId); $flags ^= 1 << $id; $this->setDataProperty($propertyId, $type, $flags); } } /** * @param int $propertyId * @param int $id * * @return bool */ public function getDataFlag($propertyId, $id){ return (((int) $this->getDataProperty($propertyId)) & (1 << $id)) > 0; } public function __destruct(){ $this->close(); } /** * @param string $metadataKey * @param MetadataValue $metadataValue */ public function setMetadata($metadataKey, MetadataValue $metadataValue){ $this->server->getEntityMetadata()->setMetadata($this, $metadataKey, $metadataValue); } /** * @param string $metadataKey * * @return MetadataValue[] */ public function getMetadata($metadataKey){ return $this->server->getEntityMetadata()->getMetadata($this, $metadataKey); } /** * @param string $metadataKey * * @return bool */ public function hasMetadata($metadataKey){ return $this->server->getEntityMetadata()->hasMetadata($this, $metadataKey); } /** * @param string $metadataKey * @param Plugin $plugin */ public function removeMetadata($metadataKey, Plugin $plugin){ $this->server->getEntityMetadata()->removeMetadata($this, $metadataKey, $plugin); } /** * @return string */ public function __toString(){ return (new \ReflectionClass($this))->getShortName() . "(" . $this->getId() . ")"; } } setMaxHealth(24); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Evoker::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::EMERALD, 0, mt_rand(0, 1)) ]; $drops[] = ItemItem::get(ItemItem::TOTEM, 0, 1); return $drops; } }namedtag->TileID)){ $this->blockId = $this->namedtag["TileID"]; }elseif(isset($this->namedtag->Tile)){ $this->blockId = $this->namedtag["Tile"]; $this->namedtag["TileID"] = new IntTag("TileID", $this->blockId); } if(isset($this->namedtag->Data)){ $this->damage = $this->namedtag["Data"]; } if($this->blockId === 0){ $this->close(); return; } $this->setDataProperty(self::DATA_VARIANT, self::DATA_TYPE_INT, $this->getBlock() | ($this->getDamage() << 8)); } /** * @param Entity $entity * * @return bool */ public function canCollideWith(Entity $entity){ return false; } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ if($source->getCause() === EntityDamageEvent::CAUSE_VOID){ parent::attack($damage, $source); } } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->timings->startTiming(); $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0 and !$this->justCreated){ return true; } $this->lastUpdate = $currentTick; $height = $this->fallDistance; $hasUpdate = $this->entityBaseTick($tickDiff); if($this->isAlive()){ $pos = (new Vector3($this->x - 0.5, $this->y, $this->z - 0.5))->round(); if($this->ticksLived === 1){ $block = $this->level->getBlock($pos); if($block->getId() !== $this->blockId){ return true; } $this->level->setBlock($pos, Block::get(0), true); } $this->motionY -= $this->gravity; $this->move($this->motionX, $this->motionY, $this->motionZ); $friction = 1 - $this->drag; $this->motionX *= $friction; $this->motionY *= 1 - $this->drag; $this->motionZ *= $friction; $pos = (new Vector3($this->x - 0.5, $this->y, $this->z - 0.5))->round(); if($this->onGround){ $this->kill(); $block = $this->level->getBlock($pos); if($block->getId() > 0 and !$block->isSolid() and !($block instanceof Liquid)){ $this->getLevel()->dropItem($this, ItemItem::get($this->getBlock(), $this->getDamage(), 1)); }else{ if($block instanceof SnowLayer){ $oldDamage = $block->getDamage(); $this->server->getPluginManager()->callEvent($ev = new EntityBlockChangeEvent($this, $block, Block::get($this->getBlock(), $this->getDamage() + $oldDamage))); }else{ $this->server->getPluginManager()->callEvent($ev = new EntityBlockChangeEvent($this, $block, Block::get($this->getBlock(), $this->getDamage()))); } if(!$ev->isCancelled()){ $this->getLevel()->setBlock($pos, $ev->getTo(), true); if($ev->getTo() instanceof Anvil){ $sound = new AnvilFallSound($this); $this->getLevel()->addSound($sound); foreach($this->level->getNearbyEntities($this->boundingBox->grow(0.1, 0.1, 0.1), $this) as $entity){ $entity->scheduleUpdate(); if(!$entity->isAlive()){ continue; } if($entity instanceof Living){ $damage = ($height - 1) * 2; if($damage > 40) $damage = 40; $ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageByEntityEvent::CAUSE_FALL, $damage, 0.1); $entity->attack($damage, $ev); } } } } } $hasUpdate = true; } $this->updateMovement(); } return $hasUpdate or !$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } /** * @return int */ public function getBlock(){ return $this->blockId; } /** * @return mixed */ public function getDamage(){ return $this->damage; } public function saveNBT(){ $this->namedtag->TileID = new IntTag("TileID", $this->blockId); $this->namedtag->Data = new ByteTag("Data", $this->damage); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = FallingSand::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } namedtag->Data)){ $this->data = $this->namedtag["Data"]; } } /** * FishingHook constructor. * * @param Level $level * @param CompoundTag $nbt * @param Entity|null $shootingEntity */ public function __construct(Level $level, CompoundTag $nbt, Entity $shootingEntity = null){ parent::__construct($level, $nbt, $shootingEntity); } /** * @param $id */ public function setData($id){ $this->data = $id; } /** * @return int */ public function getData(){ return $this->data; } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); if($this->isCollidedVertically && $this->isInsideOfWater()){ $this->motionX = 0; $this->motionY += 0.01; $this->motionZ = 0; $this->motionChanged = true; $hasUpdate = true; }elseif($this->isCollided && $this->keepMovement === true){ $this->motionX = 0; $this->motionY = 0; $this->motionZ = 0; $this->motionChanged = true; $this->keepMovement = false; $hasUpdate = true; } if($this->attractTimer === 0 && mt_rand(0, 100) <= 30){ // chance, that a fish bites $this->coughtTimer = mt_rand(5, 10) * 20; // random delay to catch fish $this->attractTimer = mt_rand(30, 100) * 20; // reset timer $this->attractFish(); if($this->shootingEntity instanceof Player) $this->shootingEntity->sendTip("A fish bites!"); }elseif($this->attractTimer > 0){ $this->attractTimer--; } if($this->coughtTimer > 0){ $this->coughtTimer--; $this->fishBites(); } $this->timings->stopTiming(); return $hasUpdate; } public function fishBites(){ if($this->shootingEntity instanceof Player){ $pk = new EntityEventPacket(); $pk->eid = $this->shootingEntity->getId();//$this or $this->shootingEntity $pk->event = EntityEventPacket::FISH_HOOK_HOOK; $this->server->broadcastPacket($this->shootingEntity->hasSpawned, $pk); } } public function attractFish(){ if($this->shootingEntity instanceof Player){ $pk = new EntityEventPacket(); $pk->eid = $this->shootingEntity->getId();//$this or $this->shootingEntity $pk->event = EntityEventPacket::FISH_HOOK_BUBBLE; $this->server->broadcastPacket($this->shootingEntity->hasSpawned, $pk); } } /** * @return bool */ public function reelLine(){ $this->damageRod = false; if($this->shootingEntity instanceof Player && $this->coughtTimer > 0){ $fishes = [ItemItem::RAW_FISH, ItemItem::RAW_SALMON, ItemItem::CLOWN_FISH, ItemItem::PUFFER_FISH]; $fish = array_rand($fishes, 1); $item = ItemItem::get($fishes[$fish]); $this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerFishEvent($this->shootingEntity, $item, $this)); if(!$ev->isCancelled()){ $this->shootingEntity->getInventory()->addItem($item); $this->shootingEntity->addXp(mt_rand(1, 6)); $this->damageRod = true; } } if($this->shootingEntity instanceof Player){ $this->shootingEntity->unlinkHookFromPlayer(); } if(!$this->closed){ $this->kill(); $this->close(); } return $this->damageRod; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = FishingHook::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } closed !== false){ return false; } if($this->willMove(100)){ if(++$this->switchDirectionTicker === $this->switchDirectionTicks){ $this->switchDirectionTicker = 0; if(mt_rand(0, 100) < 50){ $this->flyDirection = null; } } $this->lastUpdate = $currentTick; $this->timings->startTiming(); if($this->isAlive()){ if($this->y > $this->highestY and $this->flyDirection !== null){ $this->flyDirection->y = -0.5; } $inAir = !$this->isInsideOfSolid() and !$this->isInsideOfWater(); if(!$inAir){ $this->flyDirection = null; } if($this->flyDirection instanceof Vector3){ //var_dump($this->flyDirection); $this->setMotion($this->flyDirection->multiply($this->flySpeed)); }else{ $this->flyDirection = $this->generateRandomDirection(); $this->flySpeed = mt_rand(50, 100) / 500; $this->setMotion($this->flyDirection); } //$expectedPos = new Vector3($this->x + $this->motionX, $this->y + $this->motionY, $this->z + $this->motionZ); //$motion = $this->flyDirection->multiply($this->flySpeed); $this->move($this->motionX, $this->motionY, $this->motionZ); $this->updateMovement(); //$this->getLevel()->addEntityMotion($this->chunk->getX(), $this->chunk->getZ(), $this->getId(), $motion->x, $motion->y, $motion->z); //echo "EID = {$this->getId()}, motionX = $this->motionX, motionY = $this->motionY, motionZ = $this->motionZ\n"; /* if($expectedPos->distanceSquared($this) > 0){ $this->flyDirection = $this->generateRandomDirection(); $this->flySpeed = mt_rand(50, 100) / 500; } $friction = 1 - $this->drag; $this->motionX *= $friction; $this->motionY *= 1 - $this->drag; $this->motionZ *= $friction; */ $f = sqrt(($this->motionX ** 2) + ($this->motionZ ** 2)); $this->yaw = (-atan2($this->motionX, $this->motionZ) * 180 / M_PI); $this->pitch = (-atan2($f, $this->motionY) * 180 / M_PI); if($this->onGround and $this->flyDirection instanceof Vector3){ $this->flyDirection->y *= -1; } } } parent::onUpdate($currentTick); //parent::entityBaseTick(); $this->timings->stopTiming(); return !$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } /** * @return Vector3 */ private function generateRandomDirection(){ return new Vector3(mt_rand(-1000, 1000) / 1000, mt_rand(-500, 500) / 1000, mt_rand(-1000, 1000) / 1000); } public function initEntity(){ parent::initEntity(); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_BABY, false); } /** * @return bool */ public function isBaby(){ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_BABY); } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ if($source->isCancelled()){ return; } if($source->getCause() == EntityDamageEvent::CAUSE_FALL){ $source->setCancelled(); return; } parent::attack($damage, $source); } } setMaxHealth(10); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Ghast::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }setMaxHealth(30); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Guardian::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::PRISMARINE_SHARD, 0, mt_rand(1, 2)) ]; $drops[] = ItemItem::get(ItemItem::RAW_FISH, 0, mt_rand(0, 1)); return $drops; } } eid = $this->getId(); $pk->slots = [ ItemItem::get(0, 0), ItemItem::get($id, 0), ItemItem::get(0, 0), ItemItem::get(0, 0) ]; foreach($this->level->getPlayers() as $player){ $player->dataPacket($pk); } } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } skin; } /** * @return mixed */ public function getSkinId(){ return $this->skinId; } /** * @return UUID|null */ public function getUniqueId(){ return $this->uuid; } /** * @return string */ public function getRawUniqueId(){ return $this->rawUUID; } /** * @param string $str * @param string $skinId */ public function setSkin($str, $skinId){ $this->skin = $str; $this->skinId = $skinId; } /** * @return float */ public function getFood() : float{ return $this->attributeMap->getAttribute(Attribute::HUNGER)->getValue(); } /** * WARNING: This method does not check if full and may throw an exception if out of bounds. * Use {@link Human::addFood()} for this purpose * * @param float $new * * @throws \InvalidArgumentException */ public function setFood(float $new){ $attr = $this->attributeMap->getAttribute(Attribute::HUNGER); $old = $attr->getValue(); $attr->setValue($new); // ranges: 18-20 (regen), 7-17 (none), 1-6 (no sprint), 0 (health depletion) foreach([17, 6, 0] as $bound){ if(($old > $bound) !== ($new > $bound)){ $reset = true; } } if(isset($reset)){ $this->foodTickTimer = 0; } } /** * @return float */ public function getMaxFood() : float{ return $this->attributeMap->getAttribute(Attribute::HUNGER)->getMaxValue(); } /** * @param float $amount */ public function addFood(float $amount){ $attr = $this->attributeMap->getAttribute(Attribute::HUNGER); $amount += $attr->getValue(); $amount = max(min($amount, $attr->getMaxValue()), $attr->getMinValue()); $this->setFood($amount); } /** * @return float */ public function getSaturation() : float{ return $this->attributeMap->getAttribute(Attribute::SATURATION)->getValue(); } /** * WARNING: This method does not check if saturated and may throw an exception if out of bounds. * Use {@link Human::addSaturation()} for this purpose * * @param float $saturation * * @throws \InvalidArgumentException */ public function setSaturation(float $saturation){ $this->attributeMap->getAttribute(Attribute::SATURATION)->setValue($saturation); } /** * @param float $amount */ public function addSaturation(float $amount){ $attr = $this->attributeMap->getAttribute(Attribute::SATURATION); $attr->setValue($attr->getValue() + $amount, true); } /** * @return float */ public function getExhaustion() : float{ return $this->attributeMap->getAttribute(Attribute::EXHAUSTION)->getValue(); } /** * WARNING: This method does not check if exhausted and does not consume saturation/food. * Use {@link Human::exhaust()} for this purpose. * * @param float $exhaustion */ public function setExhaustion(float $exhaustion){ $this->attributeMap->getAttribute(Attribute::EXHAUSTION)->setValue($exhaustion); } /** * Increases a human's exhaustion level. * * @param float $amount * @param int $cause * * @return float the amount of exhaustion level increased */ public function exhaust(float $amount, int $cause = PlayerExhaustEvent::CAUSE_CUSTOM) : float{ $this->server->getPluginManager()->callEvent($ev = new PlayerExhaustEvent($this, $amount, $cause)); if($ev->isCancelled()){ return 0.0; } $exhaustion = $this->getExhaustion(); $exhaustion += $ev->getAmount(); while($exhaustion >= 4.0){ $exhaustion -= 4.0; $saturation = $this->getSaturation(); if($saturation > 0){ $saturation = max(0, $saturation - 1.0); $this->setSaturation($saturation); }else{ $food = $this->getFood(); if($food > 0){ $food--; $this->setFood($food); } } } $this->setExhaustion($exhaustion); return $ev->getAmount(); } /** * @return int */ public function getXpLevel() : int{ return (int) $this->attributeMap->getAttribute(Attribute::EXPERIENCE_LEVEL)->getValue(); } /** * @param int $level * * @return bool */ public function setXpLevel(int $level) : bool{ $this->server->getPluginManager()->callEvent($ev = new PlayerExperienceChangeEvent($this, $level, $this->getXpProgress())); if(!$ev->isCancelled()){ $this->attributeMap->getAttribute(Attribute::EXPERIENCE_LEVEL)->setValue($ev->getExpLevel()); return true; } return false; } /** * @param int $level * * @return bool */ public function addXpLevel(int $level) : bool{ return $this->setXpLevel($this->getXpLevel() + $level); } /** * @param int $level * * @return bool */ public function takeXpLevel(int $level) : bool{ return $this->setXpLevel($this->getXpLevel() - $level); } /** * @return float */ public function getXpProgress() : float{ return $this->attributeMap->getAttribute(Attribute::EXPERIENCE)->getValue(); } /** * @param float $progress * * @return bool */ public function setXpProgress(float $progress) : bool{ $this->attributeMap->getAttribute(Attribute::EXPERIENCE)->setValue($progress); return true; } /** * @return int */ public function getTotalXp() : int{ return $this->totalXp; } /** * Changes the total exp of a player * * @param int $xp * @param bool $syncLevel This will reset the level to be in sync with the total. Usually you don't want to do this, * because it'll mess up use of xp in anvils and enchanting tables. * * @return bool */ public function setTotalXp(int $xp, bool $syncLevel = false) : bool{ $xp &= 0x7fffffff; if($xp === $this->totalXp){ return false; } if(!$syncLevel){ $level = $this->getXpLevel(); $diff = $xp - $this->totalXp + $this->getFilledXp(); if($diff > 0){ //adding xp while($diff > ($v = self::getLevelXpRequirement($level))){ $diff -= $v; if(++$level >= 21863){ $diff = $v; //fill exp bar break; } } }else{ //taking xp while($diff < ($v = self::getLevelXpRequirement($level - 1))){ $diff += $v; if(--$level <= 0){ $diff = 0; break; } } } $progress = ($diff / $v); }else{ $values = self::getLevelFromXp($xp); $level = $values[0]; $progress = $values[1]; } $this->server->getPluginManager()->callEvent($ev = new PlayerExperienceChangeEvent($this, $level, $progress)); if(!$ev->isCancelled()){ $this->totalXp = $xp; $this->setXpLevel($ev->getExpLevel()); $this->setXpProgress($ev->getProgress()); return true; } return false; } /** * @param int $xp * @param bool $syncLevel * * @return bool */ public function addXp(int $xp, bool $syncLevel = false) : bool{ return $this->setTotalXp($this->totalXp + $xp, $syncLevel); } /** * @param int $xp * @param bool $syncLevel * * @return bool */ public function takeXp(int $xp, bool $syncLevel = false) : bool{ return $this->setTotalXp($this->totalXp - $xp, $syncLevel); } /** * @return int */ public function getRemainderXp() : int{ return self::getLevelXpRequirement($this->getXpLevel()) - $this->getFilledXp(); } /** * @return int */ public function getFilledXp() : int{ return self::getLevelXpRequirement($this->getXpLevel()) * $this->getXpProgress(); } /** * @return float */ public function recalculateXpProgress() : float{ $this->setXpProgress($progress = $this->getRemainderXp() / self::getLevelXpRequirement($this->getXpLevel())); return $progress; } /** * @return int */ public function getXpSeed() : int{ //TODO: use this for randomizing enchantments in enchanting tables return $this->xpSeed; } public function resetXpCooldown(){ $this->xpCooldown = microtime(true); } /** * @return bool */ public function canPickupXp() : bool{ return microtime(true) - $this->xpCooldown > 0.5; } /** * Returns the total amount of exp required to reach the specified level. * * @param int $level * * @return int */ public static function getTotalXpRequirement(int $level) : int{ if($level <= 16){ return ($level ** 2) + (6 * $level); }elseif($level <= 31){ return (2.5 * ($level ** 2)) - (40.5 * $level) + 360; }elseif($level <= 21863){ return (4.5 * ($level ** 2)) - (162.5 * $level) + 2220; } return PHP_INT_MAX; //prevent float returns for invalid levels on 32-bit systems } /** * Returns the amount of exp required to complete the specified level. * * @param int $level * * @return int */ public static function getLevelXpRequirement(int $level) : int{ if($level <= 16){ return (2 * $level) + 7; }elseif($level <= 31){ return (5 * $level) - 38; }elseif($level <= 21863){ return (9 * $level) - 158; } return PHP_INT_MAX; } /** * Converts a quantity of exp into a level and a progress percentage * * @param int $xp * * @return int[] */ public static function getLevelFromXp(int $xp) : array{ $xp &= 0x7fffffff; /** These values are correct up to and including level 16 */ $a = 1; $b = 6; $c = -$xp; if($xp > self::getTotalXpRequirement(16)){ /** Modify the coefficients to fit the relevant equation */ if($xp <= self::getTotalXpRequirement(31)){ /** Levels 16-31 */ $a = 2.5; $b = -40.5; $c += 360; }else{ /** Level 32+ */ $a = 4.5; $b = -162.5; $c += 2220; } } $answer = max(Math::solveQuadratic($a, $b, $c)); //Use largest result value $level = floor($answer); $progress = $answer - $level; return [$level, $progress]; } /** * @return PlayerInventory */ public function getInventory(){ return $this->inventory; } /** * @return EnderChestInventory */ public function getEnderChestInventory(){ return $this->enderChestInventory; } /** * @return FloatingInventory */ public function getFloatingInventory(){ return $this->floatingInventory; } /** * @return SimpleTransactionQueue */ public function getTransactionQueue(){ //Is creating the transaction queue ondemand a good idea? I think only if it's destroyed afterwards. hmm... if($this->transactionQueue === null){ //Potential for crashes here if a plugin attempts to use this, say for an NPC plugin or something... $this->transactionQueue = new SimpleTransactionQueue($this); } return $this->transactionQueue; } protected function initEntity(){ $this->setDataFlag(self::DATA_PLAYER_FLAGS, self::DATA_PLAYER_FLAG_SLEEP, false, self::DATA_TYPE_BYTE); $this->setDataProperty(self::DATA_PLAYER_BED_POSITION, self::DATA_TYPE_POS, [0, 0, 0], false); $inventoryContents = ($this->namedtag->Inventory ?? null); $this->inventory = new PlayerInventory($this, $inventoryContents); $this->enderChestInventory = new EnderChestInventory($this, ($this->namedtag->EnderChestInventory ?? null)); //Virtual inventory for desktop GUI crafting and anti-cheat transaction processing $this->floatingInventory = new FloatingInventory($this); if($this instanceof Player){ $this->addWindow($this->inventory, 0); }else{ if(isset($this->namedtag->NameTag)){ $this->setNameTag($this->namedtag["NameTag"]); } if(isset($this->namedtag->Skin) and $this->namedtag->Skin instanceof CompoundTag){ $this->setSkin($this->namedtag->Skin["Data"], $this->namedtag->Skin["Name"]); } $this->uuid = UUID::fromData($this->getId(), $this->getSkinData(), $this->getNameTag()); } parent::initEntity(); if(!isset($this->namedtag->foodLevel) or !($this->namedtag->foodLevel instanceof IntTag)){ $this->namedtag->foodLevel = new IntTag("foodLevel", $this->getFood()); }else{ $this->setFood($this->namedtag["foodLevel"]); } if(!isset($this->namedtag->foodExhaustionLevel) or !($this->namedtag->foodExhaustionLevel instanceof IntTag)){ $this->namedtag->foodExhaustionLevel = new FloatTag("foodExhaustionLevel", $this->getExhaustion()); }else{ $this->setExhaustion($this->namedtag["foodExhaustionLevel"]); } if(!isset($this->namedtag->foodSaturationLevel) or !($this->namedtag->foodSaturationLevel instanceof IntTag)){ $this->namedtag->foodSaturationLevel = new FloatTag("foodSaturationLevel", $this->getSaturation()); }else{ $this->setSaturation($this->namedtag["foodSaturationLevel"]); } if(!isset($this->namedtag->foodTickTimer) or !($this->namedtag->foodTickTimer instanceof IntTag)){ $this->namedtag->foodTickTimer = new IntTag("foodTickTimer", $this->foodTickTimer); }else{ $this->foodTickTimer = $this->namedtag["foodTickTimer"]; } if(!isset($this->namedtag->XpLevel) or !($this->namedtag->XpLevel instanceof IntTag)){ $this->namedtag->XpLevel = new IntTag("XpLevel", 0); } $this->setXpLevel($this->namedtag["XpLevel"]); if(!isset($this->namedtag->XpP) or !($this->namedtag->XpP instanceof FloatTag)){ $this->namedtag->XpP = new FloatTag("XpP", 0); } $this->setXpProgress($this->namedtag["XpP"]); if(!isset($this->namedtag->XpTotal) or !($this->namedtag->XpTotal instanceof IntTag)){ $this->namedtag->XpTotal = new IntTag("XpTotal", 0); } $this->totalXp = $this->namedtag["XpTotal"]; if(!isset($this->namedtag->XpSeed) or !($this->namedtag->XpSeed instanceof IntTag)){ $this->namedtag->XpSeed = new IntTag("XpSeed", mt_rand(PHP_INT_MIN, PHP_INT_MAX)); } $this->xpSeed = $this->namedtag["XpSeed"]; } /** * @return int */ public function getAbsorption() : int{ return $this->attributeMap->getAttribute(Attribute::ABSORPTION)->getValue(); } /** * @param int $absorption */ public function setAbsorption(int $absorption){ $this->attributeMap->getAttribute(Attribute::ABSORPTION)->setValue($absorption); } protected function addAttributes(){ parent::addAttributes(); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::SATURATION)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXHAUSTION)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::HUNGER)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXPERIENCE_LEVEL)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::EXPERIENCE)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::HEALTH)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::MOVEMENT_SPEED)); $this->attributeMap->addAttribute(Attribute::getAttribute(Attribute::ABSORPTION)); } /** * @param int $tickDiff * @param int $EnchantL * * @return bool */ public function entityBaseTick($tickDiff = 1, $EnchantL = 0){ if($this->getInventory() instanceof PlayerInventory){ $EnchantL = $this->getInventory()->getHelmet()->getEnchantmentLevel(Enchantment::TYPE_WATER_BREATHING); } $hasUpdate = parent::entityBaseTick($tickDiff, $EnchantL); if($this->isAlive()){ $food = $this->getFood(); $health = $this->getHealth(); if($food >= 18){ $this->foodTickTimer++; if($this->foodTickTimer >= 80 and $health < $this->getMaxHealth()){ $this->heal(1, new EntityRegainHealthEvent($this, 1, EntityRegainHealthEvent::CAUSE_SATURATION)); $this->exhaust(3.0, PlayerExhaustEvent::CAUSE_HEALTH_REGEN); $this->foodTickTimer = 0; } }elseif($food === 0){ $this->foodTickTimer++; if($this->foodTickTimer >= 80){ $diff = $this->server->getDifficulty(); $can = false; if($diff === 1){ $can = $health > 10; }elseif($diff === 2){ $can = $health > 1; }elseif($diff === 3){ $can = true; } if($can){ $this->attack(1, new EntityDamageEvent($this, EntityDamageEvent::CAUSE_STARVATION, 1)); } } } if($food <= 6){ if($this->isSprinting()){ $this->setSprinting(false); } } } return $hasUpdate; } /** * @return string */ public function getName(){ return $this->getNameTag(); } /** * @return array */ public function getDrops(){ $drops = []; if($this->inventory !== null){ foreach($this->inventory->getContents() as $item){ $drops[] = $item; } } return $drops; } public function saveNBT(){ parent::saveNBT(); $this->namedtag->Inventory = new ListTag("Inventory", []); $this->namedtag->Inventory->setTagType(NBT::TAG_Compound); if($this->inventory !== null){ //Hotbar for($slot = 0; $slot < $this->inventory->getHotbarSize(); ++$slot){ $inventorySlotIndex = $this->inventory->getHotbarSlotIndex($slot); $item = $this->inventory->getItem($inventorySlotIndex); $tag = $item->nbtSerialize($slot); $tag->TrueSlot = new ByteTag("TrueSlot", $inventorySlotIndex); $this->namedtag->Inventory[$slot] = $tag; } //Normal inventory $slotCount = $this->inventory->getSize() + $this->inventory->getHotbarSize(); for($slot = $this->inventory->getHotbarSize(); $slot < $slotCount; ++$slot){ $item = $this->inventory->getItem($slot - $this->inventory->getHotbarSize()); //As NBT, real inventory slots are slots 9-44, NOT 0-35 $this->namedtag->Inventory[$slot] = $item->nbtSerialize($slot); } //Armour for($slot = 100; $slot < 104; ++$slot){ $item = $this->inventory->getItem($this->inventory->getSize() + $slot - 100); if($item instanceof ItemItem and $item->getId() !== ItemItem::AIR){ $this->namedtag->Inventory[$slot] = $item->nbtSerialize($slot); } } } $this->namedtag->EnderChestInventory = new ListTag("EnderChestInventory", []); $this->namedtag->Inventory->setTagType(NBT::TAG_Compound); if($this->enderChestInventory !== null){ for($slot = 0; $slot < $this->enderChestInventory->getSize(); $slot++){ if(($item = $this->enderChestInventory->getItem($slot)) instanceof ItemItem){ $this->namedtag->EnderChestInventory[$slot] = $item->nbtSerialize($slot); } } } if(strlen($this->getSkinData()) > 0){ $this->namedtag->Skin = new CompoundTag("Skin", [ "Data" => new StringTag("Data", $this->getSkinData()), "Name" => new StringTag("Name", $this->getSkinId()) ]); } //Xp $this->namedtag->XpLevel = new IntTag("XpLevel", $this->getXpLevel()); $this->namedtag->XpTotal = new IntTag("XpTotal", $this->getTotalXp()); $this->namedtag->XpP = new FloatTag("XpP", $this->getXpProgress()); $this->namedtag->XpSeed = new IntTag("XpSeed", $this->getXpSeed()); //Food $this->namedtag->foodLevel = new IntTag("foodLevel", $this->getFood()); $this->namedtag->foodExhaustionLevel = new FloatTag("foodExhaustionLevel", $this->getExhaustion()); $this->namedtag->foodSaturationLevel = new FloatTag("foodSaturationLevel", $this->getSaturation()); $this->namedtag->foodTickTimer = new IntTag("foodTickTimer", $this->foodTickTimer); } /** * @param Player $player */ public function spawnTo(Player $player){ if(strlen($this->skin) < 64 * 32 * 4){ $e = new \InvalidStateException((new \ReflectionClass($this))->getShortName() . " must have a valid skin set"); $this->server->getLogger()->logException($e); $this->close(); }elseif($player !== $this and !isset($this->hasSpawned[$player->getLoaderId()])){ $this->hasSpawned[$player->getLoaderId()] = $player; if(!($this instanceof Player)){ $this->server->updatePlayerListData($this->getUniqueId(), $this->getId(), $this->getName(), $this->skinId, $this->skin, [$player]); } $pk = new AddPlayerPacket(); $pk->uuid = $this->getUniqueId(); $pk->username = $this->getName(); $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->item = $this->getInventory()->getItemInHand(); $pk->metadata = $this->dataProperties; $player->dataPacket($pk); $this->sendLinkedData(); $this->inventory->sendArmorContents($player); if(!($this instanceof Player)){ $this->server->removePlayerListData($this->getUniqueId(), [$player]); } } } public function close(){ if(!$this->closed){ if($this->getFloatingInventory() instanceof FloatingInventory){ foreach($this->getFloatingInventory()->getContents() as $craftingItem){ $this->level->dropItem($this, $craftingItem); } }else{ $this->server->getLogger()->debug("Attempted to drop a null crafting inventory\n"); } if(!($this instanceof Player) or $this->loggedIn){ foreach($this->inventory->getViewers() as $viewer){ $viewer->removeWindow($this->inventory); } } parent::close(); } } } eid = $this->getId(); $pk->type = Husk::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); Entity::spawnTo($player); } }setMaxHealth(100); parent::initEntity(); } /** * @return string */ public function getName(){ return "Iron Golem"; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ //Not affected by Looting. $drops = [ItemItem::get(ItemItem::IRON_INGOT, 0, mt_rand(3, 5))]; $drops[] = ItemItem::get(ItemItem::POPPY, 0, mt_rand(0, 2)); return $drops; } }setMaxHealth(5); $this->setHealth($this->namedtag["Health"]); if(isset($this->namedtag->Age)){ $this->age = $this->namedtag["Age"]; } if(isset($this->namedtag->PickupDelay)){ $this->pickupDelay = $this->namedtag["PickupDelay"]; } if(isset($this->namedtag->Owner)){ $this->owner = $this->namedtag["Owner"]; } if(isset($this->namedtag->Thrower)){ $this->thrower = $this->namedtag["Thrower"]; } if(!isset($this->namedtag->Item)){ $this->close(); return; } assert($this->namedtag->Item instanceof CompoundTag); $this->item = ItemItem::nbtDeserialize($this->namedtag->Item); $this->server->getPluginManager()->callEvent(new ItemSpawnEvent($this)); } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ if( $source->getCause() === EntityDamageEvent::CAUSE_VOID or $source->getCause() === EntityDamageEvent::CAUSE_FIRE_TICK or $source->getCause() === EntityDamageEvent::CAUSE_ENTITY_EXPLOSION or $source->getCause() === EntityDamageEvent::CAUSE_BLOCK_EXPLOSION ){ parent::attack($damage, $source); } } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->age++; $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0 and !$this->justCreated){ return true; } $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = $this->entityBaseTick($tickDiff); if($this->isAlive()){ if($this->pickupDelay > 0 and $this->pickupDelay < 32767){ //Infinite delay $this->pickupDelay -= $tickDiff; if($this->pickupDelay < 0){ $this->pickupDelay = 0; } } $this->motionY -= $this->gravity; if($this->checkObstruction($this->x, $this->y, $this->z)){ $hasUpdate = true; } $this->move($this->motionX, $this->motionY, $this->motionZ); $friction = 1 - $this->drag; if($this->onGround and (abs($this->motionX) > 0.00001 or abs($this->motionZ) > 0.00001)){ $friction = $this->getLevel()->getBlock($this->temporalVector->setComponents((int) floor($this->x), (int) floor($this->y - 1), (int) floor($this->z) - 1))->getFrictionFactor() * $friction; } $this->motionX *= $friction; $this->motionY *= 1 - $this->drag; $this->motionZ *= $friction; if($this->onGround){ $this->motionY *= -0.5; } if($currentTick % 5 == 0) $this->updateMovement(); if($this->age > 2000){ $this->server->getPluginManager()->callEvent($ev = new ItemDespawnEvent($this)); if($ev->isCancelled()){ $this->age = 0; }else{ $this->kill(); $hasUpdate = true; } } } $this->timings->stopTiming(); return $hasUpdate or !$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } public function saveNBT(){ parent::saveNBT(); $this->namedtag->Item = $this->item->nbtSerialize(-1, "Item"); $this->namedtag->Health = new ShortTag("Health", $this->getHealth()); $this->namedtag->Age = new ShortTag("Age", $this->age); $this->namedtag->PickupDelay = new ShortTag("PickupDelay", $this->pickupDelay); if($this->owner !== null){ $this->namedtag->Owner = new StringTag("Owner", $this->owner); } if($this->thrower !== null){ $this->namedtag->Thrower = new StringTag("Thrower", $this->thrower); } } /** * @return ItemItem */ public function getItem(){ return $this->item; } /** * @param Entity $entity * * @return bool */ public function canCollideWith(Entity $entity){ return false; } /** * @return int */ public function getPickupDelay(){ return $this->pickupDelay; } /** * @param int $delay */ public function setPickupDelay($delay){ $this->pickupDelay = $delay; } /** * @return string */ public function getOwner(){ return $this->owner; } /** * @param string $owner */ public function setOwner($owner){ $this->owner = $owner; } /** * @return string */ public function getThrower(){ return $this->thrower; } /** * @param string $thrower */ public function setThrower($thrower){ $this->thrower = $thrower; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddItemEntityPacket(); $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->item = $this->getItem(); $player->dataPacket($pk); $this->sendData($player); parent::spawnTo($player); } } eid = $this->getId(); $pk->type = LavaSlime::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }setMaxHealth(2); $this->setHealth(2); } /** * @param $tick * * @return bool */ public function onUpdate($tick){ parent::onUpdate($tick); if($this->age > 20){ $this->kill(); $this->close(); } return true; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); $pk = new ExplodePacket(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->radius = 10; $pk->records = []; $player->dataPacket($pk); parent::spawnTo($player); } public function spawnToAll(){ parent::spawnToAll(); if($this->getLevel()->getServer()->lightningFire){ $fire = ItemItem::get(ItemItem::FIRE)->getBlock(); $oldBlock = $this->getLevel()->getBlock($this); if($oldBlock instanceof Liquid){ }elseif($oldBlock->isSolid()){ $v3 = new Vector3($this->x, $this->y + 1, $this->z); }else{ $v3 = new Vector3($this->x, $this->y, $this->z); } if(isset($v3)) $this->getLevel()->setBlock($v3, $fire); foreach($this->level->getNearbyEntities($this->boundingBox->grow(4, 3, 4), $this) as $entity){ if($entity instanceof Player){ $damage = mt_rand(8, 20); $ev = new EntityDamageByEntityEvent($this, $entity, EntityDamageByEntityEvent::CAUSE_LIGHTNING, $damage); if($entity->attack($ev->getFinalDamage(), $ev) === true){ $ev->useArmors(); } $entity->setOnFire(mt_rand(3, 8)); } if($entity instanceof Creeper){ $entity->setPowered(true, $this); } } } } }namedtag->HealF)){ $this->namedtag->Health = new FloatTag("Health", (int) $this->namedtag["HealF"]); unset($this->namedtag->HealF); } if(!isset($this->namedtag->Health) or !($this->namedtag->Health instanceof FloatTag)){ $this->namedtag->Health = new FloatTag("Health", $this->getMaxHealth()); } if($this->namedtag["Health"] <= 0) $this->setHealth(20.0); else $this->setHealth($this->namedtag["Health"]); } /** * @param int $amount */ public function setHealth($amount){ $wasAlive = $this->isAlive(); parent::setHealth((float) $amount); if($this->isAlive() and !$wasAlive){ $pk = new EntityEventPacket(); $pk->eid = $this->getId(); $pk->event = EntityEventPacket::RESPAWN; $this->server->broadcastPacket($this->hasSpawned, $pk); } } public function saveNBT(){ parent::saveNBT(); $this->namedtag->Health = new FloatTag("Health", $this->getHealth()); } /** * @return mixed */ public abstract function getName(); /** * @param Entity $entity * * @return bool */ public function hasLineOfSight(Entity $entity){ //TODO: head height return true; //return $this->getLevel()->rayTraceBlocks(Vector3::createVector($this->x, $this->y + $this->height, $this->z), Vector3::createVector($entity->x, $entity->y + $entity->height, $entity->z)) === null; } /** * @param float $amount * @param EntityRegainHealthEvent $source */ public function heal($amount, EntityRegainHealthEvent $source){ parent::heal($amount, $source); if($source->isCancelled()){ return; } $this->attackTime = 0; } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ if($this->attackTime > 0 or $this->noDamageTicks > 0){ $lastCause = $this->getLastDamageCause(); if($lastCause !== null and $lastCause->getDamage() >= $damage){ $source->setCancelled(); } } parent::attack($damage, $source); if($source->isCancelled()){ return; } if($source instanceof EntityDamageByEntityEvent){ $e = $source->getDamager(); if($source instanceof EntityDamageByChildEntityEvent){ $e = $source->getChild(); } if($e->isOnFire() > 0 and !($e instanceof Player)){ $this->setOnFire(2 * $this->server->getDifficulty()); } $deltaX = $this->x - $e->x; $deltaZ = $this->z - $e->z; $this->knockBack($e, $damage, $deltaX, $deltaZ, $source->getKnockBack()); if($e instanceof Husk){ $this->addEffect(Effect::getEffect(Effect::HUNGER)->setDuration(7 * 20 * $this->server->getDifficulty())); } } $pk = new EntityEventPacket(); $pk->eid = $this->getId(); $pk->event = $this->getHealth() <= 0 ? EntityEventPacket::DEATH_ANIMATION : EntityEventPacket::HURT_ANIMATION; //Ouch! $this->server->broadcastPacket($this->hasSpawned, $pk); $this->attackTime = 10; //0.5 seconds cooldown } /** * @param Entity $attacker * @param $damage * @param $x * @param $z * @param float $base */ public function knockBack(Entity $attacker, $damage, $x, $z, $base = 0.4){ $f = sqrt($x * $x + $z * $z); if($f <= 0){ return; } $f = 1 / $f; $motion = new Vector3($this->motionX, $this->motionY, $this->motionZ); $motion->x /= 2; $motion->y /= 2; $motion->z /= 2; $motion->x += $x * $f * $base; $motion->y += $base; $motion->z += $z * $f * $base; if($motion->y > $base){ $motion->y = $base; } $this->setMotion($motion); } public function kill(){ if(!$this->isAlive()){ return; } parent::kill(); $this->server->getPluginManager()->callEvent($ev = new EntityDeathEvent($this, $this->getDrops())); foreach($ev->getDrops() as $item){ $this->getLevel()->dropItem($this, $item); } } /** * @param int $tickDiff * @param int $EnchantL * * @return bool */ public function entityBaseTick($tickDiff = 1, $EnchantL = 0){ Timings::$timerLivingEntityBaseTick->startTiming(); $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_BREATHING, !$this->isInsideOfWater()); $hasUpdate = parent::entityBaseTick($tickDiff); if($this->isAlive()){ if($this->isInsideOfSolid()){ $hasUpdate = true; $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 1); $this->attack($ev->getFinalDamage(), $ev); } $maxAir = 400 + $EnchantL * 300; $this->setDataProperty(self::DATA_MAX_AIR, self::DATA_TYPE_SHORT, $maxAir); if(!$this->hasEffect(Effect::WATER_BREATHING) and $this->isInsideOfWater()){ if($this instanceof WaterAnimal){ $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, 400); }else{ $hasUpdate = true; $airTicks = $this->getDataProperty(self::DATA_AIR) - $tickDiff; if($airTicks <= -80){ $airTicks = 0; $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_DROWNING, 2); $this->attack($ev->getFinalDamage(), $ev); } $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, min($airTicks, $maxAir)); } }else{ if($this instanceof WaterAnimal){ $hasUpdate = true; $airTicks = $this->getDataProperty(self::DATA_AIR) - $tickDiff; if($airTicks <= -80){ $airTicks = 0; $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_SUFFOCATION, 2); $this->attack($ev->getFinalDamage(), $ev); } $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, $airTicks); }else{ $this->setDataProperty(self::DATA_AIR, self::DATA_TYPE_SHORT, $maxAir); } } } if($this->attackTime > 0){ $this->attackTime -= $tickDiff; } Timings::$timerLivingEntityBaseTick->stopTiming(); return $hasUpdate; } /** * @return ItemItem[] */ public function getDrops(){ return []; } /** * @param int $maxDistance * @param int $maxLength * @param array $transparent * * @return Block[] */ public function getLineOfSight($maxDistance, $maxLength = 0, array $transparent = []){ if($maxDistance > 120){ $maxDistance = 120; } if(count($transparent) === 0){ $transparent = null; } $blocks = []; $nextIndex = 0; $itr = new BlockIterator($this->level, $this->getPosition(), $this->getDirectionVector(), $this->getEyeHeight(), $maxDistance); while($itr->valid()){ $itr->next(); $block = $itr->current(); $blocks[$nextIndex++] = $block; if($maxLength !== 0 and count($blocks) > $maxLength){ array_shift($blocks); --$nextIndex; } $id = $block->getId(); if($transparent === null){ if($id !== 0){ break; } }else{ if(!isset($transparent[$id])){ break; } } } return $blocks; } /** * @param int $maxDistance * @param array $transparent * * @return Block */ public function getTargetBlock($maxDistance, array $transparent = []){ try{ $block = $this->getLineOfSight($maxDistance, 1, $transparent)[0]; if($block instanceof Block){ return $block; } }catch(\ArrayOutOfBoundsException $e){ } return null; } } setMaxHealth(30); $this->setDataProperty(Entity::DATA_VARIANT, Entity::DATA_TYPE_INT, rand(0, 3)); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::LEATHER, 0, mt_rand(0, 2)) ]; return $drops; } } setMaxHealth(6); $this->setHealth($this->getMaxHealth()); $this->moveVector[Entity::NORTH] = new Vector3(-1, 0, 0); $this->moveVector[Entity::SOUTH] = new Vector3(1, 0, 0); $this->moveVector[Entity::EAST] = new Vector3(0, 0, -1); $this->moveVector[Entity::WEST] = new Vector3(0, 0, 1); parent::initEntity(); } /** * @return string */ public function getName(): string{ return "Minecart"; } /** * @return int */ public function getType(): int{ return self::TYPE_NORMAL; } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if ($this->closed !== false) { return false; } $tickDiff = $currentTick - $this->lastUpdate; if ($tickDiff <= 1) { return false; } $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = false; if ($this->isAlive()) { $p = $this->getLinkedEntity(); if ($p instanceof Player) { if ($this->state === Minecart::STATE_INITIAL) { $this->checkIfOnRail(); } elseif ($this->state === Minecart::STATE_ON_RAIL) { $hasUpdate = $this->forwardOnRail($p); $this->updateMovement(); } } } $this->timings->stopTiming(); return $hasUpdate or !$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } /** * Check if minecart is currently on a rail and if so center the cart. */ private function checkIfOnRail(){ for ($y = -1; $y !== 2 and $this->state === Minecart::STATE_INITIAL; $y++) { $positionToCheck = $this->temporalVector->setComponents($this->x, $this->y + $y, $this->z); $block = $this->level->getBlock($positionToCheck); if ($this->isRail($block)) { $minecartPosition = $positionToCheck->floor()->add(0.5, 0, 0.5); $this->setPosition($minecartPosition); // Move minecart to center of rail $this->state = Minecart::STATE_ON_RAIL; } } if ($this->state !== Minecart::STATE_ON_RAIL) { $this->state = Minecart::STATE_OFF_RAIL; } } /** * @param Block $rail * * @return bool */ private function isRail(Block $rail){ return ($rail !== null and in_array($rail->getId(), [Block::RAIL, Block::ACTIVATOR_RAIL, Block::DETECTOR_RAIL, Block::POWERED_RAIL])); } /** * @return null|Block */ private function getCurrentRail(){ $block = $this->getLevel()->getBlock($this); if ($this->isRail($block)) { return $block; } // Rail could be one block below descending down $down = $this->temporalVector->setComponents($this->x, $this->y - 1, $this->z); $block = $this->getLevel()->getBlock($down); if ($this->isRail($block)) { return $block; } return null; } /** * Attempt to move forward on rail given the direction the cart is already moving, or if not moving based * on the direction the player is looking. * * @param Player $player Player riding the minecart. * * @return boolean True if minecart moved, false otherwise. */ private function forwardOnRail(Player $player){ if ($this->direction === -1) { $candidateDirection = $player->getDirection(); } else { $candidateDirection = $this->direction; } $rail = $this->getCurrentRail(); if ($rail !== null) { $railType = $rail->getDamage(); $nextDirection = $this->getDirectionToMove($railType, $candidateDirection); if ($nextDirection !== -1) { $this->direction = $nextDirection; $moved = $this->checkForVertical($railType, $nextDirection); if (!$moved) { return $this->moveIfRail(); } else { return true; } } else { $this->direction = -1; // Was not able to determine direction to move, so wait for player to look in valid direction } } else { // Not able to find rail $this->state = Minecart::STATE_INITIAL; } return false; } /** * Determine the direction the minecart should move based on the candidate direction (current direction * minecart is moving, or the direction the player is looking) and the type of rail that the minecart is on. * * @param int $railType Type of rail the minecart is on. * @param int $candidateDirection Direction minecart already moving, or direction player looking. * * @return int The direction the minecart should move. */ private function getDirectionToMove($railType, $candidateDirection){ switch ($railType) { case Rail::STRAIGHT_NORTH_SOUTH: case Rail::SLOPED_ASCENDING_NORTH: case Rail::SLOPED_ASCENDING_SOUTH: switch ($candidateDirection) { case Entity::NORTH: case Entity::SOUTH: return $candidateDirection; } break; case Rail::STRAIGHT_EAST_WEST: case Rail::SLOPED_ASCENDING_EAST: case Rail::SLOPED_ASCENDING_WEST: switch ($candidateDirection) { case Entity::WEST: case Entity::EAST: return $candidateDirection; } break; case Rail::CURVED_SOUTH_EAST: switch ($candidateDirection) { case Entity::SOUTH: case Entity::EAST: return $candidateDirection; case Entity::NORTH: return $this->checkForTurn($candidateDirection, Entity::EAST); case Entity::WEST: return $this->checkForTurn($candidateDirection, Entity::SOUTH); } break; case Rail::CURVED_SOUTH_WEST: switch ($candidateDirection) { case Entity::SOUTH: case Entity::WEST: return $candidateDirection; case Entity::NORTH: return $this->checkForTurn($candidateDirection, Entity::WEST); case Entity::EAST: return $this->checkForTurn($candidateDirection, Entity::SOUTH); } break; case Rail::CURVED_NORTH_WEST: switch ($candidateDirection) { case Entity::NORTH: case Entity::WEST: return $candidateDirection; case Entity::SOUTH: return $this->checkForTurn($candidateDirection, Entity::WEST); case Entity::EAST: return $this->checkForTurn($candidateDirection, Entity::NORTH); } break; case Rail::CURVED_NORTH_EAST: switch ($candidateDirection) { case Entity::NORTH: case Entity::EAST: return $candidateDirection; case Entity::SOUTH: return $this->checkForTurn($candidateDirection, Entity::EAST); case Entity::WEST: return $this->checkForTurn($candidateDirection, Entity::NORTH); } break; } return -1; } /** * Need to alter direction on curves halfway through the turn and reset the minecart to be in the middle of * the rail again so as not to collide with nearby blocks. * * @param int $currentDirection Direction minecart currently moving * @param int $newDirection Direction minecart should turn once has hit the halfway point. * * @return int Either the current direction or the new direction depending on haw far across the rail the minecart is. */ private function checkForTurn($currentDirection, $newDirection){ switch ($currentDirection) { case Entity::NORTH: $diff = $this->x - $this->getFloorX(); if ($diff !== 0 and $diff <= .5) { $dx = ($this->getFloorX() + .5) - $this->x; $this->move($dx, 0, 0); return $newDirection; } break; case Entity::SOUTH: $diff = $this->x - $this->getFloorX(); if ($diff !== 0 and $diff >= .5) { $dx = ($this->getFloorX() + .5) - $this->x; $this->move($dx, 0, 0); return $newDirection; } break; case Entity::EAST: $diff = $this->z - $this->getFloorZ(); if ($diff !== 0 and $diff <= .5) { $dz = ($this->getFloorZ() + .5) - $this->z; $this->move(0, 0, $dz); return $newDirection; } break; case Entity::WEST: $diff = $this->z - $this->getFloorZ(); if ($diff !== 0 and $diff >= .5) { $dz = $dz = ($this->getFloorZ() + .5) - $this->z; $this->move(0, 0, $dz); return $newDirection; } break; } return $currentDirection; } /** * @param $railType * @param $currentDirection * * @return bool */ private function checkForVertical($railType, $currentDirection){ switch ($railType) { case Rail::SLOPED_ASCENDING_NORTH: switch ($currentDirection) { case Entity::NORTH: // Headed north up $diff = $this->x - $this->getFloorX(); if ($diff !== 0 and $diff <= .5) { $dx = ($this->getFloorX() - .1) - $this->x; $this->move($dx, 1, 0); return true; } break; case Entity::SOUTH: // Headed south down $diff = $this->x - $this->getFloorX(); if ($diff !== 0 and $diff >= .5) { $dx = ($this->getFloorX() + 1) - $this->x; $this->move($dx, -1, 0); return true; } break; } break; case Rail::SLOPED_ASCENDING_SOUTH: switch ($currentDirection) { case Entity::SOUTH: // Headed south up $diff = $this->x - $this->getFloorX(); if ($diff !== 0 and $diff >= .5) { $dx = ($this->getFloorX() + 1) - $this->x; $this->move($dx, 1, 0); return true; } break; case Entity::NORTH: // Headed north down $diff = $this->x - $this->getFloorX(); if ($diff !== 0 and $diff <= .5) { $dx = ($this->getFloorX() - .1) - $this->x; $this->move($dx, -1, 0); return true; } break; } break; case Rail::SLOPED_ASCENDING_EAST: switch ($currentDirection) { case Entity::EAST: // Headed east up $diff = $this->z - $this->getFloorZ(); if ($diff !== 0 and $diff <= .5) { $dz = ($this->getFloorZ() - .1) - $this->z; $this->move(0, 1, $dz); return true; } break; case Entity::WEST: // Headed west down $diff = $this->z - $this->getFloorZ(); if ($diff !== 0 and $diff >= .5) { $dz = ($this->getFloorZ() + 1) - $this->z; $this->move(0, -1, $dz); return true; } break; } break; case Rail::SLOPED_ASCENDING_WEST: switch ($currentDirection) { case Entity::WEST: // Headed west up $diff = $this->z - $this->getFloorZ(); if ($diff !== 0 and $diff >= .5) { $dz = ($this->getFloorZ() + 1) - $this->z; $this->move(0, 1, $dz); return true; } break; case Entity::EAST: // Headed east down $diff = $this->z - $this->getFloorZ(); if ($diff !== 0 and $diff <= .5) { $dz = ($this->getFloorZ() - .1) - $this->z; $this->move(0, -1, $dz); return true; } break; } break; } return false; } /** * Move the minecart as long as it will still be moving on to another piece of rail. * * @return bool True if the minecart moved. */ private function moveIfRail(){ $nextMoveVector = $this->moveVector[$this->direction]; $nextMoveVector = $nextMoveVector->multiply($this->moveSpeed); $newVector = $this->add($nextMoveVector->x, $nextMoveVector->y, $nextMoveVector->z); $possibleRail = $this->getCurrentRail(); if (in_array($possibleRail->getId(), [Block::RAIL, Block::ACTIVATOR_RAIL, Block::DETECTOR_RAIL, Block::POWERED_RAIL])) { $this->moveUsingVector($newVector); return true; } return false; } /** * Invoke the normal move code, but first need to convert the desired position vector into the * delta values from the current position. * * @param Vector3 $desiredPosition */ private function moveUsingVector(Vector3 $desiredPosition){ $dx = $desiredPosition->x - $this->x; $dy = $desiredPosition->y - $this->y; $dz = $desiredPosition->z - $this->z; $this->move($dx, $dy, $dz); } /** * @return Rail */ public function getNearestRail(){ $minX = Math::floorFloat($this->boundingBox->minX); $minY = Math::floorFloat($this->boundingBox->minY); $minZ = Math::floorFloat($this->boundingBox->minZ); $maxX = Math::ceilFloat($this->boundingBox->maxX); $maxY = Math::ceilFloat($this->boundingBox->maxY); $maxZ = Math::ceilFloat($this->boundingBox->maxZ); $rails = []; for ($z = $minZ; $z <= $maxZ; ++$z) { for ($x = $minX; $x <= $maxX; ++$x) { for ($y = $minY; $y <= $maxY; ++$y) { $block = $this->level->getBlock($this->temporalVector->setComponents($x, $y, $z)); if (in_array($block->getId(), [Block::RAIL, Block::ACTIVATOR_RAIL, Block::DETECTOR_RAIL, Block::POWERED_RAIL])) $rails[] = $block; } } } $minDistance = PHP_INT_MAX; $nearestRail = null; foreach ($rails as $rail) { $dis = $this->distance($rail); if ($dis < $minDistance) { $nearestRail = $rail; $minDistance = $dis; } } return $nearestRail; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Minecart::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y + $this->getEyeHeight() - 1.2; $pk->z = $this->z; $pk->speedX = 0; $pk->speedY = 0; $pk->speedZ = 0; $pk->yaw = 0; $pk->pitch = 0; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /*public function attack($damage, EntityDamageEvent $source){ parent::attack($damage, $source); if(!$source->isCancelled()){ $pk = new EntityEventPacket(); $pk->eid = $this->id; $pk->event = EntityEventPacket::HURT_ANIMATION; foreach($this->getLevel()->getPlayers() as $player){ $player->dataPacket($pk); } } } public function getSaveId(){ $class = new \ReflectionClass(static::class); return $class->getShortName(); }*/ } eid = $this->getId(); $pk->type = MinecartChest::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = 0; $pk->speedY = 0; $pk->speedZ = 0; $pk->yaw = 0; $pk->pitch = 0; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); Entity::spawnTo($player); } }eid = $this->getId(); $pk->type = MinecartHopper::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = 0; $pk->speedY = 0; $pk->speedZ = 0; $pk->yaw = 0; $pk->pitch = 0; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); Entity::spawnTo($player); } }eid = $this->getId(); $pk->type = MinecartTNT::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = 0; $pk->speedY = 0; $pk->speedZ = 0; $pk->yaw = 0; $pk->pitch = 0; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); Entity::spawnTo($player); } }eid = $this->getId(); $pk->type = Mooshroom::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $lootingL = 0; $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent and $cause->getDamager() instanceof Player){ $lootingL = $cause->getDamager()->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); } $drops = [ItemItem::get(ItemItem::RAW_BEEF, 0, mt_rand(1, 3 + $lootingL))]; $drops[] = ItemItem::get(ItemItem::LEATHER, 0, mt_rand(0, 2 + $lootingL)); return $drops; } } setMaxHealth(20); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Mule::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::LEATHER, 0, mt_rand(1, 2)) ]; return $drops; } } CatType)){ $nbt->CatType = new ByteTag("CatType", mt_rand(0, 3)); } parent::__construct($level, $nbt); $this->setDataProperty(self::DATA_CAT_TYPE, self::DATA_TYPE_BYTE, $this->getCatType()); } /** * @param int $type */ public function setCatType(int $type){ $this->namedtag->CatType = new ByteTag("CatType", $type); } /** * @return int */ public function getCatType() : int{ return (int) $this->namedtag["CatType"]; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } setMaxHealth(1); parent::initEntity(); if(isset($this->namedtag->Motive)){ $this->motive = $this->namedtag["Motive"]; }else $this->close(); } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool */ public function attack($damage, EntityDamageEvent $source){ parent::attack($damage, $source); if($source->isCancelled()) return false; $this->level->addParticle(new DestroyBlockParticle($this->add(0.5), Block::get(Block::LADDER))); $this->kill(); return true; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddPaintingPacket(); $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->direction = $this->getDirection(); $pk->title = $this->motive; $player->dataPacket($pk); parent::spawnTo($player); } protected function updateMovement(){ //Nothing to update, paintings cannot move. } /** * @return array */ public function getDrops(){ return [ItemItem::get(ItemItem::PAINTING, 0, 1)]; } }eid = $this->getId(); $pk->type = Pig::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); $drops = [ItemItem::get(ItemItem::RAW_PORKCHOP, 0, mt_rand(1, 3 + $lootingL))]; return $drops; } } return []; } } eid = $this->getId(); $pk->type = PigZombie::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); $pk = new MobEquipmentPacket(); $pk->eid = $this->getId(); $pk->item = new ItemItem(283); $pk->slot = 0; $pk->selectedSlot = 0; $player->dataPacket($pk); } /** * @return array */ public function getDrops(){ $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); if(mt_rand(1, 200) <= (5 + 2 * $lootingL)){ $drops[] = ItemItem::get(ItemItem::GOLD_INGOT, 0, 1); } $drops[] = ItemItem::get(ItemItem::GOLD_NUGGET, 0, mt_rand(0, 1 + $lootingL)); $drops[] = ItemItem::get(ItemItem::ROTTEN_FLESH, 0, mt_rand(0, 1 + $lootingL)); return $drops; } } return []; } }setMaxHealth(30); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = PolarBear::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ItemItem::get(ItemItem::RAW_SALMON, 0, mt_rand(0, 2))]; $drops[] = ItemItem::get(ItemItem::RAW_FISH, 0, mt_rand(0, 2)); return $drops; } }dropItem = $dropItem; } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ if($source->getCause() === EntityDamageEvent::CAUSE_VOID){ parent::attack($damage, $source); } } protected function initEntity(){ parent::initEntity(); if(isset($this->namedtag->Fuse)){ $this->fuse = $this->namedtag["Fuse"]; }else{ $this->fuse = 80; } $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_IGNITED, true); $this->setDataProperty(self::DATA_FUSE_LENGTH, self::DATA_TYPE_INT, $this->fuse); } /** * @param Entity $entity * * @return bool */ public function canCollideWith(Entity $entity){ return false; } public function saveNBT(){ parent::saveNBT(); $this->namedtag->Fuse = new ByteTag("Fuse", $this->fuse); } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->timings->startTiming(); $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0 and !$this->justCreated){ return true; } if($this->fuse % 5 === 0){ //don't spam it every tick, it's not necessary $this->setDataProperty(self::DATA_FUSE_LENGTH, self::DATA_TYPE_INT, $this->fuse); } $this->lastUpdate = $currentTick; $hasUpdate = $this->entityBaseTick($tickDiff); if($this->isAlive()){ $this->motionY -= $this->gravity; $this->move($this->motionX, $this->motionY, $this->motionZ); $friction = 1 - $this->drag; $this->motionX *= $friction; $this->motionY *= $friction; $this->motionZ *= $friction; $this->updateMovement(); if($this->onGround){ $this->motionY *= -0.5; $this->motionX *= 0.7; $this->motionZ *= 0.7; } $this->fuse -= $tickDiff; if($this->fuse <= 0){ $this->kill(); $this->explode(); } } return $hasUpdate or $this->fuse >= 0 or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } public function explode(){ $this->server->getPluginManager()->callEvent($ev = new ExplosionPrimeEvent($this, 4, $this->dropItem)); if(!$ev->isCancelled()){ $explosion = new Explosion($this, $ev->getForce(), $this, $ev->dropItem()); if($ev->isBlockBreaking()){ $explosion->explodeA(); } $explosion->explodeB(); } } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = PrimedTNT::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }shootingEntity = $shootingEntity; if($shootingEntity !== null){ $this->setDataProperty(self::DATA_SHOOTER_ID, self::DATA_TYPE_LONG, $shootingEntity->getId()); } parent::__construct($level, $nbt); } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ if($source->getCause() === EntityDamageEvent::CAUSE_VOID){ parent::attack($damage, $source); } } protected function initEntity(){ parent::initEntity(); $this->setMaxHealth(1); $this->setHealth(1); if(isset($this->namedtag->Age)){ $this->age = $this->namedtag["Age"]; } } /** * @param Entity $entity * * @return bool */ public function canCollideWith(Entity $entity){ return $entity instanceof Living and !$this->onGround; } public function saveNBT(){ parent::saveNBT(); $this->namedtag->Age = new ShortTag("Age", $this->age); } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $tickDiff = $currentTick - $this->lastUpdate; if($tickDiff <= 0 and !$this->justCreated){ return true; } $this->lastUpdate = $currentTick; $hasUpdate = $this->entityBaseTick($tickDiff); if($this->isAlive()){ $movingObjectPosition = null; if(!$this->isCollided){ $this->motionY -= $this->gravity; } $moveVector = new Vector3($this->x + $this->motionX, $this->y + $this->motionY, $this->z + $this->motionZ); $list = $this->getLevel()->getCollidingEntities($this->boundingBox->addCoord($this->motionX, $this->motionY, $this->motionZ)->expand(1, 1, 1), $this); $nearDistance = PHP_INT_MAX; $nearEntity = null; foreach($list as $entity){ if(/*!$entity->canCollideWith($this) or */ ($entity === $this->shootingEntity and $this->ticksLived < 5) ){ continue; } $axisalignedbb = $entity->boundingBox->grow(0.3, 0.3, 0.3); $ob = $axisalignedbb->calculateIntercept($this, $moveVector); if($ob === null){ continue; } $distance = $this->distanceSquared($ob->hitVector); if($distance < $nearDistance){ $nearDistance = $distance; $nearEntity = $entity; } } if($nearEntity !== null){ $movingObjectPosition = MovingObjectPosition::fromEntity($nearEntity); } if($movingObjectPosition !== null){ if($movingObjectPosition->entityHit !== null){ $this->server->getPluginManager()->callEvent(new ProjectileHitEvent($this)); $motion = sqrt($this->motionX ** 2 + $this->motionY ** 2 + $this->motionZ ** 2); $damage = ceil($motion * $this->damage); if($this instanceof Arrow and $this->isCritical()){ $damage += mt_rand(0, (int) ($damage / 2) + 1); } if($this->shootingEntity === null){ $ev = new EntityDamageByEntityEvent($this, $movingObjectPosition->entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); }else{ $ev = new EntityDamageByChildEntityEvent($this->shootingEntity, $this, $movingObjectPosition->entityHit, EntityDamageEvent::CAUSE_PROJECTILE, $damage); } if($movingObjectPosition->entityHit->attack($ev->getFinalDamage(), $ev) === true){ if($this instanceof Arrow and $this->getPotionId() != 0){ foreach(Potion::getEffectsById($this->getPotionId() - 1) as $effect){ $movingObjectPosition->entityHit->addEffect($effect->setDuration($effect->getDuration() / 8)); } } $ev->useArmors(); } $this->hadCollision = true; if($this->fireTicks > 0){ $ev = new EntityCombustByEntityEvent($this, $movingObjectPosition->entityHit, 5); $this->server->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $movingObjectPosition->entityHit->setOnFire($ev->getDuration()); } } $this->kill(); return true; } } $this->move($this->motionX, $this->motionY, $this->motionZ); if($this->isCollided and !$this->hadCollision){ $this->hadCollision = true; $this->motionX = 0; $this->motionY = 0; $this->motionZ = 0; $this->server->getPluginManager()->callEvent(new ProjectileHitEvent($this)); }elseif(!$this->isCollided and $this->hadCollision){ $this->hadCollision = false; } if(!$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001){ $f = sqrt(($this->motionX ** 2) + ($this->motionZ ** 2)); $this->yaw = (atan2($this->motionX, $this->motionZ) * 180 / M_PI); $this->pitch = (atan2($this->motionY, $f) * 180 / M_PI); $hasUpdate = true; } $this->updateMovement(); } return $hasUpdate; } }setMaxHealth(3); parent::initEntity(); } /** * Rabbit constructor. * * @param Level $level * @param CompoundTag $nbt */ public function __construct(Level $level, CompoundTag $nbt){ if(!isset($nbt->RabbitType)){ $nbt->RabbitType = new ByteTag("RabbitType", $this->getRandomRabbitType()); } parent::__construct($level, $nbt); $this->setDataProperty(self::DATA_RABBIT_TYPE, self::DATA_TYPE_BYTE, $this->getRabbitType()); } /** * @return int */ public function getRandomRabbitType() : int{ $arr = [0, 1, 2, 3, 4, 5, 99]; return $arr[mt_rand(0, count($arr) - 1)]; } /** * @param int $type */ public function setRabbitType(int $type){ $this->namedtag->RabbitType = new ByteTag("RabbitType", $type); } /** * @return int */ public function getRabbitType() : int{ return (int) $this->namedtag["RabbitType"]; } /** * @return string */ public function getName() : string{ return "Rabbit"; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Rabbit::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $lootingL = 0; $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); } } $drops = [ItemItem::get(ItemItem::RABBIT_HIDE, 0, mt_rand(0, 1))]; if($this->getLastDamageCause() === EntityDamageEvent::CAUSE_FIRE){ $drops[] = ItemItem::get(ItemItem::COOKED_RABBIT, 0, mt_rand(0, 1)); }else{ $drops[] = ItemItem::get(ItemItem::RAW_RABBIT, 0, mt_rand(0, 1)); } if(mt_rand(1, 200) <= (5 + 2 * $lootingL)){ $drops[] = ItemItem::get(ItemItem::RABBIT_FOOT, 0, 1); } return $drops; } } Color)) { $nbt->Color = new ByteTag("Color", self::getRandomColor()); } parent::__construct($level, $nbt); $this->setDataProperty(self::DATA_COLOR_INFO, self::DATA_TYPE_BYTE, $this->getColor()); } /** * @return int */ public static function getRandomColor(): int{ $rand = ""; $rand .= str_repeat(Wool::WHITE . " ", 20); $rand .= str_repeat(Wool::ORANGE . " ", 5); $rand .= str_repeat(Wool::MAGENTA . " ", 5); $rand .= str_repeat(Wool::LIGHT_BLUE . " ", 5); $rand .= str_repeat(Wool::YELLOW . " ", 5); $rand .= str_repeat(Wool::GRAY . " ", 10); $rand .= str_repeat(Wool::LIGHT_GRAY . " ", 10); $rand .= str_repeat(Wool::CYAN . " ", 5); $rand .= str_repeat(Wool::PURPLE . " ", 5); $rand .= str_repeat(Wool::BLUE . " ", 5); $rand .= str_repeat(Wool::BROWN . " ", 5); $rand .= str_repeat(Wool::GREEN . " ", 5); $rand .= str_repeat(Wool::RED . " ", 5); $rand .= str_repeat(Wool::BLACK . " ", 10); $arr = explode(" ", $rand); return intval($arr[mt_rand(0, count($arr) - 1)]); } /** * @return int */ public function getColor(): int{ return (int)$this->namedtag["Color"]; } /** * @param int $color */ public function setColor(int $color){ $this->namedtag->Color = new ByteTag("Color", $color); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Sheep::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); $drops = [ItemItem::get(ItemItem::WOOL, $this->getColor(), 1)]; $drops[] = ItemItem::get(ItemItem::RAW_MUTTON, 0, mt_rand(1, 2 + $lootingL)); return $drops; } } return []; } } setMaxHealth(30); $this->setDataProperty(Entity::DATA_VARIANT, Entity::DATA_TYPE_INT, 10); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Shulker::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::SHULKER_SHELL, 0, mt_rand(0, 1)) ]; return $drops; } } eid = $this->getId(); $pk->type = Silverfish::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }eid = $this->getId(); $pk->type = Skeleton::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); $pk = new MobEquipmentPacket(); $pk->eid = $this->getId(); $pk->item = new ItemItem(ItemItem::BOW); $pk->slot = 0; $pk->selectedSlot = 0; $player->dataPacket($pk); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::ARROW, 0, mt_rand(0, 2)) ]; $drops[] = ItemItem::get(ItemItem::BONE, 0, mt_rand(0, 2)); return $drops; } } setMaxHealth(30); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = SkeletonHorse::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } eid = $this->getId(); $pk->type = Slime::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ItemItem::get(ItemItem::SLIMEBALL, 0, 1)]; if($this->lastDamageCause instanceof EntityDamageByEntityEvent and $this->lastDamageCause->getEntity() instanceof Player){ if(\mt_rand(0, 199) < 5){ switch(\mt_rand(0, 2)){ case 0: $drops[] = ItemItem::get(ItemItem::IRON_INGOT, 0, 1); break; case 1: $drops[] = ItemItem::get(ItemItem::CARROT, 0, 1); break; case 2: $drops[] = ItemItem::get(ItemItem::POTATO, 0, 1); break; } } } return $drops; } }setMaxHealth(4); parent::initEntity(); } /** * @return string */ public function getName(){ return "Snow Golem"; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = self::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }closed){ return false; } $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); if($this->age > 1200 or $this->isCollided){ $this->kill(); $hasUpdate = true; } $this->timings->stopTiming(); return $hasUpdate; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = Snowball::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }eid = $this->getId(); $pk->type = Spider::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ItemItem::get(ItemItem::STRING, 0, 1)]; if($this->lastDamageCause instanceof EntityDamageByEntityEvent and $this->lastDamageCause->getEntity() instanceof Player){ if(mt_rand(0, 199) < 5){ switch(mt_rand(0, 2)){ case 0: $drops[] = ItemItem::get(ItemItem::IRON_INGOT, 0, 1); break; case 1: $drops[] = ItemItem::get(ItemItem::CARROT, 0, 1); break; case 2: $drops[] = ItemItem::get(ItemItem::POTATO, 0, 1); break; } } } return $drops; } }setMaxHealth(5); } /** * @return string */ public function getName() : string{ return "Squid"; } /** * @param float $damage * @param EntityDamageEvent $source * * @return bool|void */ public function attack($damage, EntityDamageEvent $source){ parent::attack($damage, $source); if($source->isCancelled()){ return; } if($source instanceof EntityDamageByEntityEvent){ $this->swimSpeed = mt_rand(150, 350) / 2000; $e = $source->getDamager(); $this->swimDirection = (new Vector3($this->x - $e->x, $this->y - $e->y, $this->z - $e->z))->normalize(); $pk = new EntityEventPacket(); $pk->eid = $this->getId(); $pk->event = EntityEventPacket::SQUID_INK_CLOUD; $this->server->broadcastPacket($this->hasSpawned, $pk); } } /** * @return Vector3 */ private function generateRandomDirection(){ return new Vector3(mt_rand(-1000, 1000) / 1000, mt_rand(-500, 500) / 1000, mt_rand(-1000, 1000) / 1000); } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed !== false){ return false; } if(++$this->switchDirectionTicker === 100){ $this->switchDirectionTicker = 0; if(mt_rand(0, 100) < 50){ $this->swimDirection = null; } } $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); if($this->isAlive()){ if($this->y > 62 and $this->swimDirection !== null){ $this->swimDirection->y = -0.5; } $inWater = $this->isInsideOfWater(); if(!$inWater){ $this->motionY -= $this->gravity; $this->swimDirection = null; }elseif($this->swimDirection !== null){ if($this->motionX ** 2 + $this->motionY ** 2 + $this->motionZ ** 2 <= $this->swimDirection->lengthSquared()){ $this->motionX = $this->swimDirection->x * $this->swimSpeed; $this->motionY = $this->swimDirection->y * $this->swimSpeed; $this->motionZ = $this->swimDirection->z * $this->swimSpeed; } }else{ $this->swimDirection = $this->generateRandomDirection(); $this->swimSpeed = mt_rand(50, 100) / 2000; } $expectedPos = new Vector3($this->x + $this->motionX, $this->y + $this->motionY, $this->z + $this->motionZ); $this->move($this->motionX, $this->motionY, $this->motionZ); if($expectedPos->distanceSquared($this) > 0){ $this->swimDirection = $this->generateRandomDirection(); $this->swimSpeed = mt_rand(50, 100) / 2000; } $friction = 1 - $this->drag; $this->motionX *= $friction; $this->motionY *= 1 - $this->drag; $this->motionZ *= $friction; $f = sqrt(($this->motionX ** 2) + ($this->motionZ ** 2)); $this->yaw = (-atan2($this->motionX, $this->motionZ) * 180 / M_PI); $this->pitch = (-atan2($f, $this->motionY) * 180 / M_PI); if($this->onGround){ $this->motionY *= -0.5; } } $this->timings->stopTiming(); return $hasUpdate or !$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Squid::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $lootingL = 0; $cause = $this->lastDamageCause; if($cause instanceof EntityDamageByEntityEvent and $cause->getDamager() instanceof Player){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); $drops = [ItemItem::get(ItemItem::DYE, 0, mt_rand(1, 3 + $lootingL))]; return $drops; } } return []; } } eid = $this->getId(); $pk->type = Stray::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); Entity::spawnTo($player); $pk = new MobEquipmentPacket(); $pk->eid = $this->getId(); $pk->item = new ItemItem(ItemItem::BOW); $pk->slot = 0; $pk->selectedSlot = 0; $player->dataPacket($pk); } } hasSplashed){ $this->hasSplashed = true; $this->getLevel()->addParticle(new SpellParticle($this, 46, 82, 153)); if($this->getLevel()->getServer()->expEnabled){ $this->getLevel()->spawnXPOrb($this->add(0, -0.2, 0), mt_rand(1, 4)); $this->getLevel()->spawnXPOrb($this->add(-0.1, -0.2, 0), mt_rand(1, 4)); $this->getLevel()->spawnXPOrb($this->add(0, -0.2, -0.1), mt_rand(1, 4)); } $this->kill(); } } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); $this->age++; if($this->age > 1200 or $this->isCollided){ $this->splash(); $hasUpdate = true; } $this->timings->stopTiming(); return $hasUpdate; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = ThrownExpBottle::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }PotionId)){ $nbt->PotionId = new ShortTag("PotionId", Potion::AWKWARD); } parent::__construct($level, $nbt, $shootingEntity); unset($this->dataProperties[self::DATA_SHOOTER_ID]); $this->setDataProperty(self::DATA_POTION_ID, self::DATA_TYPE_SHORT, $this->getPotionId()); } /** * @return int */ public function getPotionId() : int{ return (int) $this->namedtag["PotionId"]; } public function splash(){ if(!$this->hasSplashed){ $this->hasSplashed = true; $color = Potion::getColor($this->getPotionId()); $this->getLevel()->addParticle(new SpellParticle($this, $color[0], $color[1], $color[2])); $radius = 6; foreach ($this->getLevel()->getNearbyEntities($this->getBoundingBox()->grow($radius, $radius, $radius)) as $p) { foreach(Potion::getEffectsById($this->getPotionId()) as $effect){ $p->addEffect($effect); } } $this->kill(); } } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $this->timings->startTiming(); $hasUpdate = parent::onUpdate($currentTick); $this->age++; if($this->age > 1200 or $this->isCollided){ $this->splash(); $hasUpdate = true; } $this->timings->stopTiming(); return $hasUpdate; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = ThrownPotion::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } setMaxHealth(14); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Vex::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }Profession)){ $nbt->Profession = new ByteTag("Profession", mt_rand(0, 4)); } parent::__construct($level, $nbt); $this->setDataProperty(self::DATA_PROFESSION_ID, self::DATA_TYPE_BYTE, $this->getProfession()); } protected function initEntity(){ parent::initEntity(); if(!isset($this->namedtag->Profession)){ $this->setProfession(self::PROFESSION_FARMER); } } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Villager::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * Sets the villager profession * * @param int $profession */ public function setProfession(int $profession){ $this->namedtag->Profession = new ByteTag("Profession", $profession); } /** * @return int */ public function getProfession() : int{ $pro = (int) $this->namedtag["Profession"]; return min(4, max(0, $pro)); } /** * @return bool */ public function isBaby(){ return $this->getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_BABY); } } setMaxHealth(24); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Vindicator::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::EMERALD, 0, mt_rand(0, 1)) ]; return $drops; } }getDataFlag(self::DATA_FLAGS, self::DATA_FLAG_BABY); } } setMaxHealth(26); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Witch::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ //TODO return []; } }setMaxHealth(300); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Wither::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } //TODO: 添加出生和死亡情景 /** * @return array */ public function getDrops(){ $drops = [ItemItem::get(ItemItem::NETHER_STAR, 0, 1)]; return $drops; } } setMaxHealth(20); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = WitherSkeleton::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $drops = [ ItemItem::get(ItemItem::COAL, 0, mt_rand(0, 1)) ]; $drops[] = ItemItem::get(ItemItem::BONE, 0, mt_rand(0, 2)); return $drops; } }eid = $this->getId(); $pk->type = Wolf::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } namedtag->Experience)){ $this->experience = $this->namedtag["Experience"]; }else $this->close(); } /** * @param $currentTick * * @return bool */ public function onUpdate($currentTick){ if($this->closed){ return false; } $tickDiff = $currentTick - $this->lastUpdate; $this->lastUpdate = $currentTick; $this->timings->startTiming(); $hasUpdate = $this->entityBaseTick($tickDiff); $this->age++; if($this->age > 1200){ $this->kill(); $this->close(); $hasUpdate = true; } $minDistance = PHP_INT_MAX; $target = null; foreach($this->getViewers() as $p){ if(!$p->isSpectator() and $p->isAlive()){ if(($dist = $p->distance($this)) < $minDistance and $dist < $this->range){ $target = $p; $minDistance = $dist; } } } if($target !== null){ $moveSpeed = 0.7; $motX = ($target->getX() - $this->x) / 8; $motY = ($target->getY() + $target->getEyeHeight() - $this->y) / 8; $motZ = ($target->getZ() - $this->z) / 8; $motSqrt = sqrt($motX * $motX + $motY * $motY + $motZ * $motZ); $motC = 1 - $motSqrt; if($motC > 0){ $motC *= $motC; $this->motionX = $motX / $motSqrt * $motC * $moveSpeed; $this->motionY = $motY / $motSqrt * $motC * $moveSpeed; $this->motionZ = $motZ / $motSqrt * $motC * $moveSpeed; } $this->motionY -= $this->gravity; if($this->checkObstruction($this->x, $this->y, $this->z)){ $hasUpdate = true; } if($this->isInsideOfSolid()){ $this->setPosition($target); } if($minDistance <= 1.3){ if($this->getLevel()->getServer()->expEnabled and $target->canPickupXp()){ $this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new PlayerPickupExpOrbEvent($target, $this->getExperience())); if(!$ev->isCancelled()){ $this->kill(); $this->close(); if($this->getExperience() > 0){ $target->level->addSound(new ExpPickupSound($target, mt_rand(0, 1000))); $target->addXp($this->getExperience()); $target->resetXpCooldown(); } } } } } $this->move($this->motionX, $this->motionY, $this->motionZ); $this->updateMovement(); $this->timings->stopTiming(); return $hasUpdate or !$this->onGround or abs($this->motionX) > 0.00001 or abs($this->motionY) > 0.00001 or abs($this->motionZ) > 0.00001; } /** * @param Entity $entity * * @return bool */ public function canCollideWith(Entity $entity){ return false; } /** * @param $exp */ public function setExperience($exp){ $this->experience = $exp; } /** * @return int */ public function getExperience(){ return $this->experience; } /** * @param Player $player */ public function spawnTo(Player $player){ $this->setDataFlag(self::DATA_FLAGS, self::DATA_FLAG_NO_AI, true); $pk = new AddEntityPacket(); $pk->type = XPOrb::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } setMaxHealth(20); parent::initEntity(); } /** * @param $currentTick * * @return bool */ /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = Zombie::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } /** * @return array */ public function getDrops(){ $cause = $this->lastDamageCause; $drops = []; if($cause instanceof EntityDamageByEntityEvent){ $damager = $cause->getDamager(); if($damager instanceof Player){ $lootingL = $damager->getItemInHand()->getEnchantmentLevel(Enchantment::TYPE_WEAPON_LOOTING); if(mt_rand(0, 199) < (5 + 2 * $lootingL)){ switch(mt_rand(0, 3)){ case 0: $drops[] = ItemItem::get(ItemItem::IRON_INGOT, 0, 1); break; case 1: $drops[] = ItemItem::get(ItemItem::CARROT, 0, 1); break; case 2: $drops[] = ItemItem::get(ItemItem::POTATO, 0, 1); break; } } $count = mt_rand(0, 2 + $lootingL); if($count > 0){ $drops[] = ItemItem::get(ItemItem::ROTTEN_FLESH, 0, $count); } } } return $drops; } } setMaxHealth(20); parent::initEntity(); } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->eid = $this->getId(); $pk->type = ZombieHorse::NETWORK_ID; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->yaw = $this->yaw; $pk->pitch = $this->pitch; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } } setMaxHealth(20); parent::initEntity(); } /** * @return string */ public function getName() : string{ return "Zombie Villager"; } /** * @param Player $player */ public function spawnTo(Player $player){ $pk = new AddEntityPacket(); $pk->type = ZombieVillager::NETWORK_ID; $pk->eid = $this->getId(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->speedX = $this->motionX; $pk->speedY = $this->motionY; $pk->speedZ = $this->motionZ; $pk->metadata = $this->dataProperties; $player->dataPacket($pk); parent::spawnTo($player); } }eventName === null ? get_class($this) : $this->eventName; } /** * @return bool * * @throws \BadMethodCallException */ public function isCancelled(){ if(!($this instanceof Cancellable)){ throw new \BadMethodCallException("Event is not Cancellable"); } /** @var Event $this */ return $this->isCancelled === true; } /** * @param bool $value * * @throws \BadMethodCallException */ public function setCancelled($value = true){ if(!($this instanceof Cancellable)){ throw new \BadMethodCallException("Event is not Cancellable"); } /** @var Event $this */ $this->isCancelled = (bool) $value; } /** * @return HandlerList */ public function getHandlers(){ if(static::$handlerList === null){ static::$handlerList = new HandlerList(); } return static::$handlerList; } } LOW -> NORMAL -> HIGH -> HIGHEST -> MONITOR * * MONITOR events should not change the event outcome or contents */ abstract class EventPriority { /** * Event call is of very low importance and should be ran first, to allow * other plugins to further customise the outcome */ const LOWEST = 5; /** * Event call is of low importance */ const LOW = 4; /** * Event call is neither important or unimportant, and may be ran normally */ const NORMAL = 3; /** * Event call is of high importance */ const HIGH = 2; /** * Event call is critical and must have the final say in what happens * to the event */ const HIGHEST = 1; /** * Event is listened to purely for monitoring the outcome of an event. * * No modifications to the event should be made under this priority */ const MONITOR = 0; }bake(); } } /** * Unregisters all the listeners * If a Plugin or Listener is passed, all the listeners with that object will be removed * * @param Plugin|Listener|null $object */ public static function unregisterAll($object = null){ if($object instanceof Listener or $object instanceof Plugin){ foreach(self::$allLists as $h){ $h->unregister($object); } }else{ foreach(self::$allLists as $h){ foreach($h->handlerSlots as $key => $list){ $h->handlerSlots[$key] = []; } $h->handlers = null; } } } /** * HandlerList constructor. */ public function __construct(){ $this->handlerSlots = [ EventPriority::LOWEST => [], EventPriority::LOW => [], EventPriority::NORMAL => [], EventPriority::HIGH => [], EventPriority::HIGHEST => [], EventPriority::MONITOR => [] ]; self::$allLists[] = $this; } /** * @param RegisteredListener $listener * * @throws \Throwable */ public function register(RegisteredListener $listener){ if($listener->getPriority() < EventPriority::MONITOR or $listener->getPriority() > EventPriority::LOWEST){ return; } if(isset($this->handlerSlots[$listener->getPriority()][spl_object_hash($listener)])){ throw new \InvalidStateException("This listener is already registered to priority " . $listener->getPriority()); } $this->handlers = null; $this->handlerSlots[$listener->getPriority()][spl_object_hash($listener)] = $listener; } /** * @param RegisteredListener[] $listeners */ public function registerAll(array $listeners){ foreach($listeners as $listener){ $this->register($listener); } } /** * @param RegisteredListener|Listener|Plugin $object */ public function unregister($object){ if($object instanceof Plugin or $object instanceof Listener){ $changed = false; foreach($this->handlerSlots as $priority => $list){ foreach($list as $hash => $listener){ if(($object instanceof Plugin and $listener->getPlugin() === $object) or ($object instanceof Listener and $listener->getListener() === $object) ){ unset($this->handlerSlots[$priority][$hash]); $changed = true; } } } if($changed === true){ $this->handlers = null; } }elseif($object instanceof RegisteredListener){ if(isset($this->handlerSlots[$object->getPriority()][spl_object_hash($object)])){ unset($this->handlerSlots[$object->getPriority()][spl_object_hash($object)]); $this->handlers = null; } } } public function bake(){ if($this->handlers !== null){ return; } $entries = []; foreach($this->handlerSlots as $list){ foreach($list as $hash => $listener){ $entries[$hash] = $listener; } } $this->handlers = $entries; } /** * @param null|Plugin $plugin * * @return RegisteredListener[] */ public function getRegisteredListeners($plugin = null){ if($plugin !== null){ $listeners = []; foreach($this->getRegisteredListeners(null) as $hash => $listener){ if($listener->getPlugin() === $plugin){ $listeners[$hash] = $plugin; } } return $listeners; }else{ while(($handlers = $this->handlers) === null){ $this->bake(); } return $handlers; } } /** * @return HandlerList[] */ public static function getHandlerLists(){ return self::$allLists; } } getFolderName() . " - "; $this->setBlock = new TimingsHandler("** " . $name . "setBlock"); $this->doBlockLightUpdates = new TimingsHandler("** " . $name . "doBlockLightUpdates"); $this->doBlockSkyLightUpdates = new TimingsHandler("** " . $name . "doBlockSkyLightUpdates"); $this->mobSpawn = new TimingsHandler("** " . $name . "mobSpawn"); $this->doChunkUnload = new TimingsHandler("** " . $name . "doChunkUnload"); $this->doTickPending = new TimingsHandler("** " . $name . "doTickPending"); $this->doTickTiles = new TimingsHandler("** " . $name . "doTickTiles"); $this->doVillages = new TimingsHandler("** " . $name . "doVillages"); $this->doChunkMap = new TimingsHandler("** " . $name . "doChunkMap"); $this->doSounds = new TimingsHandler("** " . $name . "doSounds"); $this->doChunkGC = new TimingsHandler("** " . $name . "doChunkGC"); $this->doPortalForcer = new TimingsHandler("** " . $name . "doPortalForcer"); $this->entityTick = new TimingsHandler("** " . $name . "entityTick"); $this->tileEntityTick = new TimingsHandler("** " . $name . "tileEntityTick"); $this->tileEntityPending = new TimingsHandler("** " . $name . "tileEntityPending"); $this->syncChunkSendTimer = new TimingsHandler("** " . $name . "syncChunkSend"); $this->syncChunkSendPrepareTimer = new TimingsHandler("** " . $name . "syncChunkSendPrepare"); $this->syncChunkLoadTimer = new TimingsHandler("** " . $name . "syncChunkLoad"); $this->syncChunkLoadDataTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Data"); $this->syncChunkLoadStructuresTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Structures"); $this->syncChunkLoadEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Entities"); $this->syncChunkLoadTileEntitiesTimer = new TimingsHandler("** " . $name . "syncChunkLoad - TileEntities"); $this->syncChunkLoadTileTicksTimer = new TimingsHandler("** " . $name . "syncChunkLoad - TileTicks"); $this->syncChunkLoadPostTimer = new TimingsHandler("** " . $name . "syncChunkLoad - Post"); $this->tracker = new TimingsHandler($name . "tracker"); $this->doTick = new TimingsHandler($name . "doTick"); $this->tickEntities = new TimingsHandler($name . "tickEntities"); } }text = $text; } /** * @param $text */ public function setText($text){ $this->text = $text; } /** * @return string */ public function getText(){ return $this->text; } /** * @return string */ public function __toString(){ return $this->getText(); } }getTask(); if($ftask instanceof PluginTask and $ftask->getOwner() !== null){ $plugin = $ftask->getOwner()->getDescription()->getFullName(); }elseif($task->timingName !== null){ $plugin = "Scheduler"; }else{ $plugin = "Unknown"; } $taskname = $task->getTaskName(); $name = "Task: " . $plugin . " Runnable: " . $taskname; if($period > 0){ $name .= "(interval:" . $period . ")"; }else{ $name .= "(Single)"; } if(!isset(self::$pluginTaskTimingMap[$name])){ self::$pluginTaskTimingMap[$name] = new TimingsHandler($name, self::$schedulerSyncTimer); } return self::$pluginTaskTimingMap[$name]; } /** * @param Entity $entity * * @return TimingsHandler */ public static function getEntityTimings(Entity $entity){ $entityType = (new \ReflectionClass($entity))->getShortName(); if(!isset(self::$entityTypeTimingMap[$entityType])){ if($entity instanceof Player){ self::$entityTypeTimingMap[$entityType] = new TimingsHandler("** tickEntity - EntityPlayer", self::$tickEntityTimer); }else{ self::$entityTypeTimingMap[$entityType] = new TimingsHandler("** tickEntity - " . $entityType, self::$tickEntityTimer); } } return self::$entityTypeTimingMap[$entityType]; } /** * @param Tile $tile * * @return TimingsHandler */ public static function getTileEntityTimings(Tile $tile){ $tileType = (new \ReflectionClass($tile))->getShortName(); if(!isset(self::$tileEntityTypeTimingMap[$tileType])){ self::$tileEntityTypeTimingMap[$tileType] = new TimingsHandler("** tickTileEntity - " . $tileType, self::$tickTileEntityTimer); } return self::$tileEntityTypeTimingMap[$tileType]; } /** * @param DataPacket $pk * * @return TimingsHandler */ public static function getReceiveDataPacketTimings(DataPacket $pk){ if(!isset(self::$packetReceiveTimingMap[$pk::NETWORK_ID])){ $pkName = (new \ReflectionClass($pk))->getShortName(); self::$packetReceiveTimingMap[$pk::NETWORK_ID] = new TimingsHandler("** receivePacket - " . $pkName . " [0x" . dechex($pk::NETWORK_ID) . "]", self::$playerNetworkReceiveTimer); } return self::$packetReceiveTimingMap[$pk::NETWORK_ID]; } /** * @param DataPacket $pk * * @return TimingsHandler */ public static function getSendDataPacketTimings(DataPacket $pk){ if(!isset(self::$packetSendTimingMap[$pk::NETWORK_ID])){ $pkName = (new \ReflectionClass($pk))->getShortName(); self::$packetSendTimingMap[$pk::NETWORK_ID] = new TimingsHandler("** sendPacket - " . $pkName . " [0x" . dechex($pk::NETWORK_ID) . "]", self::$playerNetworkTimer); } return self::$packetSendTimingMap[$pk::NETWORK_ID]; } }name = $name; if($parent !== null){ $this->parent = $parent; } self::$HANDLERS[spl_object_hash($this)] = $this; } /** * @param $fp */ public static function printTimings($fp){ fwrite($fp, "Minecraft" . PHP_EOL); foreach(self::$HANDLERS as $timings){ $time = $timings->totalTime; $count = $timings->count; if($count === 0){ continue; } $avg = $time / $count; fwrite($fp, " " . $timings->name . " Time: " . round($time * 1000000000) . " Count: " . $count . " Avg: " . round($avg * 1000000000) . " Violations: " . $timings->violations . PHP_EOL); } fwrite($fp, "# Version " . Server::getInstance()->getVersion() . PHP_EOL); fwrite($fp, "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion() . PHP_EOL); $entities = 0; $livingEntities = 0; foreach(Server::getInstance()->getLevels() as $level){ $entities += count($level->getEntities()); foreach($level->getEntities() as $e){ if($e instanceof Living){ ++$livingEntities; } } } fwrite($fp, "# Entities " . $entities . PHP_EOL); fwrite($fp, "# LivingEntities " . $livingEntities . PHP_EOL); } public static function reload(){ if(Server::getInstance()->getPluginManager()->useTimings()){ foreach(self::$HANDLERS as $timings){ $timings->reset(); } TimingsCommand::$timingStart = microtime(true); } } /** * @param bool $measure */ public static function tick($measure = true){ if(PluginManager::$useTimings){ if($measure){ foreach(self::$HANDLERS as $timings){ if($timings->curTickTotal > 0.05){ $timings->violations += round($timings->curTickTotal / 0.05); } $timings->curTickTotal = 0; $timings->curCount = 0; $timings->timingDepth = 0; } }else{ foreach(self::$HANDLERS as $timings){ $timings->totalTime -= $timings->curTickTotal; $timings->count -= $timings->curCount; $timings->curTickTotal = 0; $timings->curCount = 0; $timings->timingDepth = 0; } } } } public function startTiming(){ if(PluginManager::$useTimings and ++$this->timingDepth === 1){ $this->start = microtime(true); if($this->parent !== null and ++$this->parent->timingDepth === 1){ $this->parent->start = $this->start; } } } public function stopTiming(){ if(PluginManager::$useTimings){ if(--$this->timingDepth !== 0 or $this->start === 0){ return; } $diff = microtime(true) - $this->start; $this->totalTime += $diff; $this->curTickTotal += $diff; ++$this->curCount; ++$this->count; $this->start = 0; if($this->parent !== null){ $this->parent->stopTiming(); } } } public function reset(){ $this->count = 0; $this->curCount = 0; $this->violations = 0; $this->curTickTotal = 0; $this->totalTime = 0; $this->start = 0; $this->timingDepth = 0; } public function remove(){ unset(self::$HANDLERS[spl_object_hash($this)]); } }setParameters($params); } /** * @return string[] */ public function getParameters(){ return $this->params; } /** * @param int $i * * @return string */ public function getParameter($i){ return isset($this->params[$i]) ? $this->params[$i] : null; } /** * @param int $i * @param string $str */ public function setParameter($i, $str){ if($i < 0 or $i > count($this->params)){ //Intended, allow to set the last throw new \InvalidArgumentException("Invalid index $i, have " . count($this->params)); } $this->params[(int) $i] = $str; } /** * @param string[] $params */ public function setParameters(array $params){ $i = 0; foreach($params as $str){ $this->params[$i] = (string) $str; ++$i; } } }block = $block; $this->item = $item; $this->player = $player; $this->instaBreak = (bool) $instaBreak; $drops = $player->isSurvival() ? $block->getDrops($item) : []; if($drops != null && is_numeric($drops[0])) $this->blockDrops[] = Item::get($drops[0], $drops[1], $drops[2]); else foreach($drops as $i){ $this->blockDrops[] = Item::get($i[0], $i[1], $i[2]); } } /** * @return Player */ public function getPlayer(){ return $this->player; } /** * @return Item */ public function getItem(){ return $this->item; } /** * @return bool */ public function getInstaBreak(){ return $this->instaBreak; } /** * @return Item[] */ public function getDrops(){ return $this->blockDrops; } /** * @param Item[] $drops */ public function setDrops(array $drops){ $this->blockDrops = $drops; } /** * @param bool $instaBreak */ public function setInstaBreak($instaBreak){ $this->instaBreak = (bool) $instaBreak; } } block = $block; } /** * @return Block */ public function getBlock(){ return $this->block; } }newState = $newState; } /** * @return Block */ public function getNewState(){ return $this->newState; } }block = $blockPlace; $this->blockReplace = $blockReplace; $this->blockAgainst = $blockAgainst; $this->item = $item; $this->player = $player; } /** * @return Player */ public function getPlayer(){ return $this->player; } /** * Gets the item in hand * * @return mixed */ public function getItem(){ return $this->item; } /** * @return Block */ public function getBlockReplaced(){ return $this->blockReplace; } /** * @return Block */ public function getBlockAgainst(){ return $this->blockAgainst; } }source = $source; } /** * @return Block */ public function getSource(){ return $this->source; } }player = $player; $this->block = $block; $this->itemFrame = $itemFrame; $this->item = $item; } /** * @return Player */ public function getPlayer(){ return $this->player; } /** * @return ItemFrame */ public function getItemFrame(){ return $this->itemFrame; } /** * @return Item */ public function getItem(){ return $this->item; } } player = $thePlayer; $this->lines = $theLines; } /** * @return Player */ public function getPlayer(){ return $this->player; } /** * @return string[] */ public function getLines(){ return $this->lines; } /** * @param int $index 0-3 * * @return string */ public function getLine($index){ return $this->lines[$index]; } /** * @param int $index 0-3 * @param string $line */ public function setLine($index, $line){ $this->lines[$index] = $line; } }entity = $creeper; $this->lightning = $lightning; $this->cause = $cause; } /** * @return Lightning */ public function getLightning(){ return $this->lightning; } /** * @return int */ public function getCause(){ return $this->cause; } } entity = $entity; $this->oldItem = $oldItem; $this->newItem = $newItem; $this->slot = (int) $slot; } /** * @return int */ public function getSlot(){ return $this->slot; } /** * @return Item */ public function getNewItem(){ return $this->newItem; } /** * @param Item $item */ public function setNewItem(Item $item){ $this->newItem = $item; } /** * @return Item */ public function getOldItem(){ return $this->oldItem; } }entity = $entity; $this->from = $from; $this->to = $to; } /** * @return Block */ public function getBlock(){ return $this->from; } /** * @return Block */ public function getTo(){ return $this->to; } }combuster = $combuster; } /** * @return Block */ public function getCombuster(){ return $this->combuster; } }combuster = $combuster; } /** * @return Entity */ public function getCombuster(){ return $this->combuster; } }entity = $combustee; $this->duration = $duration; $this->ProtectLevel = $ProtectLevel; } /** * @return float|int */ public function getDuration(){ if($this->ProtectLevel !== 0){ return round($this->duration * (1 - 0.15 * $this->ProtectLevel)); }else{ return $this->duration; } } /** * @param $duration */ public function setDuration($duration){ $this->duration = (int) $duration; } /** * @param $ProtectLevel */ public function setProtectLevel($ProtectLevel){ $this->ProtectLevel = (int) $ProtectLevel; } }damager = $damager; parent::__construct($entity, $cause, $damage); } /** * @return Block */ public function getDamager(){ return $this->damager; } }childEntity = $childEntity; parent::__construct($damager, $entity, $cause, $damage); } /** * @return Entity */ public function getChild(){ return $this->childEntity; } }damager = $damager; $this->knockBack = $knockBack; parent::__construct($entity, $cause, $damage); $this->addAttackerModifiers($damager); } /** * @param Entity $damager */ protected function addAttackerModifiers(Entity $damager){ if($damager->hasEffect(Effect::STRENGTH)){ $this->setRateDamage(1 + 0.3 * ($damager->getEffect(Effect::STRENGTH)->getAmplifier() + 1), self::MODIFIER_STRENGTH); } if($damager->hasEffect(Effect::WEAKNESS)){ $eff_level = 1 - 0.2 * ($damager->getEffect(Effect::WEAKNESS)->getAmplifier() + 1); if($eff_level < 0){ $eff_level = 0; } $this->setRateDamage($eff_level, self::MODIFIER_WEAKNESS); } } /** * @return Entity */ public function getDamager(){ return $this->damager; } /** * @return float */ public function getKnockBack(){ return $this->knockBack; } /** * @param float $knockBack */ public function setKnockBack($knockBack){ $this->knockBack = $knockBack; } } entity = $entity; $this->cause = $cause; if(is_array($damage)){ $this->modifiers = $damage; }else{ $this->modifiers = [ self::MODIFIER_BASE => $damage ]; } $this->originals = $this->modifiers; if(!isset($this->modifiers[self::MODIFIER_BASE])){ throw new \InvalidArgumentException("BASE Damage modifier missing"); } //For DAMAGE_RESISTANCE if($cause !== self::CAUSE_VOID and $cause !== self::CAUSE_SUICIDE){ if($entity->hasEffect(Effect::DAMAGE_RESISTANCE)){ $RES_level = 1 - 0.20 * ($entity->getEffect(Effect::DAMAGE_RESISTANCE)->getAmplifier() + 1); if($RES_level < 0){ $RES_level = 0; } $this->setRateDamage($RES_level, self::MODIFIER_RESISTANCE); } } //TODO: add zombie if($entity instanceof Player and $entity->getInventory() instanceof PlayerInventory){ switch($cause){ case self::CAUSE_CONTACT: case self::CAUSE_ENTITY_ATTACK: case self::CAUSE_PROJECTILE: case self::CAUSE_FIRE: case self::CAUSE_LAVA: case self::CAUSE_BLOCK_EXPLOSION: case self::CAUSE_ENTITY_EXPLOSION: case self::CAUSE_LIGHTNING: $points = 0; foreach($entity->getInventory()->getArmorContents() as $index => $i){ if($i->isArmor()){ $points += $i->getArmorValue(); $this->usedArmors[$index] = 1; } } if($points !== 0){ $this->setRateDamage(1 - 0.04 * $points, self::MODIFIER_ARMOR); } //For Protection $spe_Prote = null; switch($cause){ case self::CAUSE_ENTITY_EXPLOSION: case self::CAUSE_BLOCK_EXPLOSION: $spe_Prote = Enchantment::TYPE_ARMOR_EXPLOSION_PROTECTION; break; case self::CAUSE_FIRE: case self::CAUSE_LAVA: $spe_Prote = Enchantment::TYPE_ARMOR_FIRE_PROTECTION; break; case self::CAUSE_PROJECTILE: $spe_Prote = Enchantment::TYPE_ARMOR_PROJECTILE_PROTECTION; break; default; break; } foreach($this->usedArmors as $index => $cost){ $i = $entity->getInventory()->getArmorItem($index); if($i->isArmor()){ $this->EPF += $i->getEnchantmentLevel(Enchantment::TYPE_ARMOR_PROTECTION); $this->fireProtectL = max($this->fireProtectL, $i->getEnchantmentLevel(Enchantment::TYPE_ARMOR_FIRE_PROTECTION)); if($i->getEnchantmentLevel(Enchantment::TYPE_ARMOR_THORNS) > 0){ $this->thornsLevel[$index] = $i->getEnchantmentLevel(Enchantment::TYPE_ARMOR_THORNS); } if($spe_Prote !== null){ $this->EPF += 2 * $i->getEnchantmentLevel($spe_Prote); } } } break; case self::CAUSE_FALL: //Feather Falling $i = $entity->getInventory()->getBoots(); if($i->isArmor()){ $this->EPF += $i->getEnchantmentLevel(Enchantment::TYPE_ARMOR_PROTECTION); $this->EPF += 3 * $i->getEnchantmentLevel(Enchantment::TYPE_ARMOR_FALL_PROTECTION); } break; case self::CAUSE_FIRE_TICK: case self::CAUSE_SUFFOCATION: case self::CAUSE_DROWNING: case self::CAUSE_VOID: case self::CAUSE_SUICIDE: case self::CAUSE_MAGIC: case self::CAUSE_CUSTOM: case self::CAUSE_STARVATION: break; default: break; } if($this->EPF !== 0){ $this->EPF = min(20, ceil($this->EPF * mt_rand(50, 100) / 100)); $this->setRateDamage(1 - 0.04 * $this->EPF, self::MODIFIER_PROTECTION); } } } /** * @return int */ public function getCause(){ return $this->cause; } /** * @param int $type * * @return int */ public function getOriginalDamage($type = self::MODIFIER_BASE){ if(isset($this->originals[$type])){ return $this->originals[$type]; } return 0; } /** * @param int $type * * @return int */ public function getDamage($type = self::MODIFIER_BASE){ if(isset($this->modifiers[$type])){ return $this->modifiers[$type]; } return 0; } /** * @param float $damage * @param int $type * * @throws \UnexpectedValueException */ public function setDamage($damage, $type = self::MODIFIER_BASE){ $this->modifiers[$type] = $damage; } /** * @param int $type * * @return float 1 - the percentage */ public function getRateDamage($type = self::MODIFIER_BASE){ if(isset($this->rateModifiers[$type])){ return $this->rateModifiers[$type]; } return 1; } /** * @param float $damage * @param int $type * * Notice:If you want to add/reduce the damage without reducing by Armor or effect. set a new Damage using setDamage * Notice:If you want to add/reduce the damage within reducing by Armor of effect. Plz change the MODIFIER_BASE * Notice:If you want to add/reduce the damage by multiplying. Plz use this function. */ public function setRateDamage($damage, $type = self::MODIFIER_BASE){ $this->rateModifiers[$type] = $damage; } /** * @param int $type * * @return bool */ public function isApplicable($type){ return isset($this->modifiers[$type]); } /** * @return int */ public function getFinalDamage(){ $damage = $this->modifiers[self::MODIFIER_BASE]; foreach($this->rateModifiers as $type => $d){ $damage *= $d; } foreach($this->modifiers as $type => $d){ if($type !== self::MODIFIER_BASE){ $damage += $d; } } return $damage; } /** * @return Item $usedArmors * notice: $usedArmors $index->$cost * $index: the $index of ArmorInventory * $cost: the num of durability cost */ public function getUsedArmors(){ return $this->usedArmors; } /** * @return Int $fireProtectL */ public function getFireProtectL(){ return $this->fireProtectL; } /** * @return bool */ public function useArmors(){ if($this->entity instanceof Player){ if($this->entity->isSurvival() and $this->entity->isAlive()){ foreach($this->usedArmors as $index => $cost){ $i = $this->entity->getInventory()->getArmorItem($index); if($i->isArmor()){ $this->entity->getInventory()->damageArmor($index, $cost); } } } return true; } return false; } public function createThornsDamage(){ if($this->thornsLevel !== []){ $this->thornsArmor = array_rand($this->thornsLevel); $thornsL = $this->thornsLevel[$this->thornsArmor]; if(mt_rand(1, 100) < $thornsL * 15){ //$this->thornsDamage = mt_rand(1, 4); $this->thornsDamage = 0; //Delete When #321 Is Fixed And Add In The Normal Damage } } } /** * @return int */ public function getThornsDamage(){ return $this->thornsDamage; } /** * @return bool should be used after getThornsDamage() */ public function setThornsArmorUse(){ if($this->thornsArmor === null){ return false; }else{ $this->usedArmors[$this->thornsArmor] = 3; return true; } } } entity = $entity; $this->drops = $drops; } /** * @return Living */ public function getEntity(){ return $this->entity; } /** * @return \pocketmine\item\Item[] */ public function getDrops(){ return $this->drops; } /** * @param Item[] $drops */ public function setDrops(array $drops){ $this->drops = $drops; } }entity = $entity; $this->entityType = $entity::NETWORK_ID; } /** * @return int */ public function getType(){ return $this->entityType; } /** * @return bool */ public function isCreature(){ return $this->entity instanceof Creature; } /** * @return bool */ public function isHuman(){ return $this->entity instanceof Human; } /** * @return bool */ public function isProjectile(){ return $this->entity instanceof Projectile; } /** * @return bool */ public function isVehicle(){ return $this->entity instanceof Vehicle; } /** * @return bool */ public function isItem(){ return $this->entity instanceof Item; } }entity = $entity; $this->potion = $potion; $this->effects = $potion->getEffects(); } /** * @return array|Effect[] */ public function getEffects(){ return $this->effects; } /** * @return Potion */ public function getPotion(){ return $this->potion; } } entity = $entity; $this->foodSource = $foodSource; $this->foodRestore = $foodSource->getFoodRestore(); $this->saturationRestore = $foodSource->getSaturationRestore(); $this->residue = $foodSource->getResidue(); $this->additionalEffects = $foodSource->getAdditionalEffects(); } /** * @return FoodSource */ public function getFoodSource(){ return $this->foodSource; } /** * @return int */ public function getFoodRestore() : int{ return $this->foodRestore; } /** * @param int $foodRestore */ public function setFoodRestore(int $foodRestore){ $this->foodRestore = $foodRestore; } /** * @return float */ public function getSaturationRestore() : float{ return $this->saturationRestore; } /** * @param float $saturationRestore */ public function setSaturationRestore(float $saturationRestore){ $this->saturationRestore = $saturationRestore; } /** * @return mixed */ public function getResidue(){ return $this->residue; } /** * @param $residue */ public function setResidue($residue){ $this->residue = $residue; } /** * @return Effect[] */ public function getAdditionalEffects(){ return $this->additionalEffects; } /** * @param Effect[] $additionalEffects * * @throws \TypeError */ public function setAdditionalEffects(array $additionalEffects){ foreach($additionalEffects as $effect){ if(!($effect instanceof Effect)){ throw new \TypeError("Argument 1 passed to EntityEatEvent::setAdditionalEffects() must be an Effect array"); } } $this->additionalEffects = $additionalEffects; } } entity = $entity; $this->effect = $effect; } /** * @return Effect */ public function getEffect(){ return $this->effect; } } entity = $entity; $this->effect = $effect; } /** * @return Effect */ public function getEffect(){ return $this->effect; } } entity; } }entity = $entity; $this->position = $position; $this->blocks = $blocks; $this->yield = $yield; } /** * @return Position */ public function getPosition(){ return $this->position; } /** * @return Block[] */ public function getBlockList(){ return $this->blocks; } /** * @param Block[] $blocks */ public function setBlockList(array $blocks){ $this->blocks = $blocks; } /** * @return float */ public function getYield(){ return $this->yield; } /** * @param float $yield */ public function setYield($yield){ $this->yield = $yield; } }position = $pos; $this->entityType = $entityType; $this->cause = $cause; } /** * @return Position */ public function getPosition(){ return $this->position; } /** * @param Position $pos */ public function setPosition(Position $pos){ $this->position = $pos; } /** * @return int */ public function getType() : int{ return $this->entityType; } /** * @return int */ public function getCause() : int{ return $this->cause; } }entity = $entity; $this->oldItem = $oldItem; $this->newItem = $newItem; $this->slot = (int) $slot; } /** * @return int */ public function getSlot(){ return $this->slot; } /** * @return Item */ public function getNewItem(){ return $this->newItem; } /** * @param Item $item */ public function setNewItem(Item $item){ $this->newItem = $item; } /** * @return Item */ public function getOldItem(){ return $this->oldItem; } }entity = $entity; $this->originLevel = $originLevel; $this->targetLevel = $targetLevel; } /** * @return Level */ public function getOrigin(){ return $this->originLevel; } /** * @return Level */ public function getTarget(){ return $this->targetLevel; } }entity = $entity; $this->mot = $mot; } /** * @return Vector3 */ public function getVector(){ return $this->mot; } } entity = $entity; $this->amount = $amount; $this->reason = (int) $regainReason; } /** * @return float */ public function getAmount(){ return $this->amount; } /** * @param float $amount */ public function setAmount($amount){ $this->amount = $amount; } /** * @return int */ public function getRegainReason(){ return $this->reason; } }entity = $shooter; $this->bow = $bow; $this->projectile = $projectile; $this->force = $force; } /** * @return Living */ public function getEntity(){ return $this->entity; } /** * @return Item */ public function getBow(){ return $this->bow; } /** * @return Entity|Projectile */ public function getProjectile(){ return $this->projectile; } /** * @param Entity $projectile */ public function setProjectile(Entity $projectile){ if($projectile !== $this->projectile){ if(count($this->projectile->getViewers()) === 0){ $this->projectile->kill(); $this->projectile->close(); } $this->projectile = $projectile; } } /** * @return float */ public function getForce(){ return $this->force; } /** * @param float $force */ public function setForce($force){ $this->force = $force; } }entity = $entity; $this->entityType = $entity::NETWORK_ID; } /** * @return \pocketmine\level\Position */ public function getPosition(){ return $this->entity->getPosition(); } /** * @return int */ public function getType(){ return $this->entityType; } /** * @return bool */ public function isCreature(){ return $this->entity instanceof Creature; } /** * @return bool */ public function isHuman(){ return $this->entity instanceof Human; } /** * @return bool */ public function isProjectile(){ return $this->entity instanceof Projectile; } /** * @return bool */ public function isVehicle(){ return $this->entity instanceof Vehicle; } /** * @return bool */ public function isItem(){ return $this->entity instanceof Item; } }entity = $entity; $this->from = $from; $this->to = $to; } /** * @return Position */ public function getFrom(){ return $this->from; } /** * @param Position $from */ public function setFrom(Position $from){ $this->from = $from; } /** * @return Position */ public function getTo(){ return $this->to; } /** * @param Position $to */ public function setTo(Position $to){ $this->to = $to; } }entity = $entity; $this->force = $force; $this->blockBreaking = true; $this->dropItem = $dropItem; } /** * @param bool $dropItem */ public function setDropItem(bool $dropItem){ $this->dropItem = $dropItem; } /** * @return bool */ public function dropItem() : bool{ return $this->dropItem; } /** * @return float */ public function getForce(){ return $this->force; } /** * @param $force */ public function setForce($force){ $this->force = (float) $force; } /** * @return bool */ public function isBlockBreaking(){ return $this->blockBreaking; } /** * @param bool $affectsBlocks */ public function setBlockBreaking($affectsBlocks){ $this->blockBreaking = (bool) $affectsBlocks; } }entity = $item; } /** * @return Item */ public function getEntity(){ return $this->entity; } }entity = $item; } /** * @return Item */ public function getEntity(){ return $this->entity; } }entity = $entity; } /** * @return Projectile */ public function getEntity(){ return $this->entity; } }entity = $entity; } /** * @return Projectile */ public function getEntity(){ return $this->entity; } }inventory = $inventory; } /** * @return EventName|string */ public function getName(){ return "AnvilProcessEvent"; } } player = $player; $this->input = $input; $this->recipe = $recipe; } /** * @return Item[] */ public function getInput(){ $items = []; foreach($this->input as $i => $item){ $items[$i] = clone $item; } return $items; } /** * @return Recipe */ public function getRecipe(){ return $this->recipe; } /** * @return \pocketmine\Player */ public function getPlayer(){ return $this->player; } }getBlock()); $this->fuel = $fuel; $this->burnTime = (int) $burnTime; $this->furnace = $furnace; } /** * @return Furnace */ public function getFurnace(){ return $this->furnace; } /** * @return Item */ public function getFuel(){ return $this->fuel; } /** * @return int */ public function getBurnTime(){ return $this->burnTime; } /** * @param int $burnTime */ public function setBurnTime($burnTime){ $this->burnTime = (int) $burnTime; } /** * @return bool */ public function isBurning(){ return $this->burning; } /** * @param bool $burning */ public function setBurning($burning){ $this->burning = (bool) $burning; } }getBlock()); $this->source = clone $source; $this->source->setCount(1); $this->result = $result; $this->furnace = $furnace; } /** * @return Furnace */ public function getFurnace(){ return $this->furnace; } /** * @return Item */ public function getSource(){ return $this->source; } /** * @return Item */ public function getResult(){ return $this->result; } /** * @param Item $result */ public function setResult(Item $result){ $this->result = $result; } }who = $who; parent::__construct($inventory); } /** * @return Player */ public function getPlayer(){ return $this->who; } }inventory = $inventory; } /** * @return Inventory */ public function getInventory(){ return $this->inventory; } /** * @return \pocketmine\entity\Human[] */ public function getViewers(){ return $this->inventory->getViewers(); } }who = $who; parent::__construct($inventory); } /** * @return Player */ public function getPlayer(){ return $this->who; } }arrow = $arrow; parent::__construct($inventory); } /** * @return Arrow */ public function getArrow(){ return $this->arrow; } }item = $item; parent::__construct($inventory); } /** * @return Item */ public function getItem(){ return $this->item; } }transactionQueue = $transactionQueue; } /** * @deprecated * @return TransactionQueue */ public function getTransaction(){ return $this->transactionQueue; } /** * @return TransactionQueue */ public function getQueue(){ return $this->transactionQueue; } }chunk = $chunk; } /** * @return Chunk */ public function getChunk(){ return $this->chunk; } }newChunk = $newChunk; } /** * @return bool */ public function isNewChunk(){ return $this->newChunk; } }level = $level; } /** * @return \pocketmine\level\Level */ public function getLevel(){ return $this->level; } }previousSpawn = $previousSpawn; } /** * @return Position */ public function getPreviousSpawn(){ return $this->previousSpawn; } }weather = $weather; $this->duration = $duration; } /** * @return int */ public function getWeather() : int{ return $this->weather; } /** * @param int $weather */ public function setWeather(int $weather = Weather::SUNNY){ $this->weather = $weather; } /** * @return int */ public function getDuration() : int{ return $this->duration; } /** * @param int $duration */ public function setDuration(int $duration){ $this->duration = $duration; } }player = $player; $this->achievement = $achievementId; } /** * @return string */ public function getAchievement(){ return $this->achievement; } }player = $player; $this->animationType = $animation; } /** * @return int */ public function getAnimationType(){ return $this->animationType; } } player = $player; $this->bed = $bed; } /** * @return Block */ public function getBed(){ return $this->bed; } }player = $player; $this->bed = $bed; } /** * @return Block */ public function getBed(){ return $this->bed; } }player = $who; $this->blockClicked = $blockClicked; $this->blockFace = (int) $blockFace; $this->item = $itemInHand; $this->bucket = $bucket; } /** * Returns the bucket used in this event * * @return Item */ public function getBucket(){ return $this->bucket; } /** * Returns the item in hand after the event * * @return Item */ public function getItem(){ return $this->item; } /** * @param Item $item */ public function setItem(Item $item){ $this->item = $item; } /** * @return Block */ public function getBlockClicked(){ return $this->blockClicked; } /** * @return int */ public function getBlockFace(){ return $this->blockFace; } }player = $player; $this->message = $message; $this->format = $format; if($recipients === null){ $this->recipients = Server::getInstance()->getPluginManager()->getPermissionSubscriptions(Server::BROADCAST_CHANNEL_USERS); }else{ $this->recipients = $recipients; } } /** * @return string */ public function getMessage(){ return $this->message; } /** * @param $message */ public function setMessage($message){ $this->message = $message; } /** * Changes the player that is sending the message * * @param Player $player */ public function setPlayer(Player $player){ $this->player = $player; } /** * @return string */ public function getFormat(){ return $this->format; } /** * @param $format */ public function setFormat($format){ $this->format = $format; } /** * @return array|Player[] */ public function getRecipients(){ return $this->recipients; } /** * @param array $recipients */ public function setRecipients(array $recipients){ $this->recipients = $recipients; } }player = $player; $this->message = $message; } /** * @return string */ public function getMessage(){ return $this->message; } /** * @param string $message */ public function setMessage($message){ $this->message = $message; } /** * @param Player $player */ public function setPlayer(Player $player){ $this->player = $player; } }interface = $interface; $this->clientId = $clientId; $this->address = $address; $this->port = $port; if(!is_a($baseClass, Player::class, true)){ throw new \RuntimeException("Base class $baseClass must extend " . Player::class); } $this->baseClass = $baseClass; if(!is_a($playerClass, Player::class, true)){ throw new \RuntimeException("Class $playerClass must extend " . Player::class); } $this->playerClass = $playerClass; } /** * @return SourceInterface */ public function getInterface(){ return $this->interface; } /** * @return string */ public function getAddress(){ return $this->address; } /** * @return int */ public function getPort(){ return $this->port; } /** * @return mixed */ public function getClientId(){ return $this->clientId; } /** * @return Player::class */ public function getBaseClass(){ return $this->baseClass; } /** * @param Player ::class $class */ public function setBaseClass($class){ if(!is_a($class, $this->baseClass, true)){ throw new \RuntimeException("Base class $class must extend " . $this->baseClass); } $this->baseClass = $class; } /** * @return Player::class */ public function getPlayerClass(){ return $this->playerClass; } /** * @param Player ::class $class */ public function setPlayerClass($class){ if(!is_a($class, $this->baseClass, true)){ throw new \RuntimeException("Class $class must extend " . $this->baseClass); } $this->playerClass = $class; } }deathMessage = $deathMessage; } /** * @return Player */ public function getEntity(){ return $this->entity; } /** * @return Player */ public function getPlayer(){ return $this->entity; } /** * @return TextContainer|string */ public function getDeathMessage(){ return $this->deathMessage; } /** * @param string|TextContainer $deathMessage */ public function setDeathMessage($deathMessage){ $this->deathMessage = $deathMessage; } /** * @return bool */ public function getKeepInventory() : bool{ return $this->keepInventory; } /** * @param bool $keepInventory */ public function setKeepInventory(bool $keepInventory){ $this->keepInventory = $keepInventory; } /** * @return bool */ public function getKeepExperience() : bool{ return $this->keepExperience; } /** * @param bool $keepExperience */ public function setKeepExperience(bool $keepExperience){ $this->keepExperience = $keepExperience; } }player = $player; $this->drop = $drop; } /** * @return Item */ public function getItem(){ return $this->drop; } }player; } }player = $human; $this->amount = $amount; } /** * @return Human|Player */ public function getPlayer(){ return $this->player; } /** * @return float */ public function getAmount() : float{ return $this->amount; } /** * @param float $amount */ public function setAmount(float $amount){ $this->amount = $amount; } } progress = $progress; $this->expLevel = $expLevel; $this->player = $player; } /** * @return int */ public function getExpLevel(){ return $this->expLevel; } /** * @param $level */ public function setExpLevel($level){ $this->expLevel = $level; } /** * @return float */ public function getProgress() : float{ return $this->progress; } /** * @param float $progress */ public function setProgress(float $progress){ $this->progress = $progress; } /** * @return int */ public function getExp(){ return Human::getLevelXpRequirement($this->expLevel) + $this->progress; } /** * @param $exp */ public function setExp($exp){ $this->progress = $exp / Human::getLevelXpRequirement($this->expLevel); } } player = $player; $this->item = $item; $this->hook = $fishingHook; } /** * @return Item */ public function getItem(){ return clone $this->item; } /** * @return null|FishingHook */ public function getHook(){ return $this->hook; } } player = $player; $this->gamemode = (int) $newGamemode; } /** * @return int */ public function getNewGamemode(){ return $this->gamemode; } }player = $Player; $this->target = $target; $this->item = $itemInHand; } /** * @return Item */ public function getItem(){ return $this->item; } /** * @param Item $item */ public function setItem(Item $item){ $this->item = $item; } /** * @return Block */ public function getBlock(){ return $this->target; } }data = $data; $this->player = $player; } /** * @return mixed */ public function getData(){ return $this->data; } /** * @param $data */ public function setData($data){ $this->data = $data; } } blockTouched = $block; $this->touchVector = new Vector3(0, 0, 0); }else{ $this->touchVector = $block; $this->blockTouched = Block::get(0, 0, new Position(0, 0, 0, $player->level)); } $this->player = $player; $this->item = $item; $this->blockFace = (int) $face; $this->action = (int) $action; } /** * @return int */ public function getAction(){ return $this->action; } /** * @return Item */ public function getItem(){ return $this->item; } /** * @return Block */ public function getBlock(){ return $this->blockTouched; } /** * @return Vector3 */ public function getTouchVector(){ return $this->touchVector; } /** * @return int */ public function getFace(){ return $this->blockFace; } } player = $player; $this->item = $item; } /** * @return Item */ public function getItem(){ return clone $this->item; } }player = $player; $this->item = $item; $this->inventorySlot = (int) $inventorySlot; $this->slot = (int) $slot; } /** * @return int */ public function getSlot(){ return $this->slot; } /** * @return int */ public function getInventorySlot(){ return $this->inventorySlot; } /** * @return Item */ public function getItem(){ return $this->item; } }player = $player; $this->joinMessage = $joinMessage; } /** * @param string|TextContainer $joinMessage */ public function setJoinMessage($joinMessage){ $this->joinMessage = $joinMessage; } /** * @return string|TextContainer */ public function getJoinMessage(){ return $this->joinMessage; } }player = $player; $this->quitMessage = $quitMessage; $this->reason = $reason; } /** * @return string */ public function getReason(){ return $this->reason; } /** * @param $quitMessage */ public function setQuitMessage($quitMessage){ $this->quitMessage = $quitMessage; } /** * @return string */ public function getQuitMessage(){ return $this->quitMessage; } }player = $player; $this->kickMessage = $kickMessage; } /** * @param $kickMessage */ public function setKickMessage($kickMessage){ $this->kickMessage = $kickMessage; } /** * @return string */ public function getKickMessage(){ return $this->kickMessage; } }player = $player; $this->from = $from; $this->to = $to; } /** * @return Location */ public function getFrom(){ return $this->from; } /** * @param Location $from */ public function setFrom(Location $from){ $this->from = $from; } /** * @return Location */ public function getTo(){ return $this->to; } /** * @param Location $to */ public function setTo(Location $to){ $this->to = $to; } }player = $p; $this->amount = $amount; } /** * @return int */ public function getAmount() : int{ return $this->amount; } /** * @param int $amount */ public function setAmount(int $amount){ $this->amount = $amount; } }player = $player; $this->kickMessage = $kickMessage; } /** * @param $kickMessage */ public function setKickMessage($kickMessage){ $this->kickMessage = $kickMessage; } /** * @return string */ public function getKickMessage(){ return $this->kickMessage; } }player = $player; $this->quitMessage = $quitMessage; $this->autoSave = $autoSave; } /** * @param $quitMessage */ public function setQuitMessage($quitMessage){ $this->quitMessage = $quitMessage; } /** * @return string */ public function getQuitMessage(){ return $this->quitMessage; } /** * @return bool */ public function getAutoSave(){ return $this->autoSave; } /** * @param bool $value */ public function setAutoSave($value = true){ $this->autoSave = (bool) $value; } }player = $player; $this->position = $position; } /** * @return Position */ public function getRespawnPosition(){ return $this->position; } /** * @param Position $position */ public function setRespawnPosition(Position $position){ $this->position = $position; } }player = $player; $this->message = $message; $this->type = $type; } /** * @return mixed */ public function getMessage(){ return $this->message; } /** * @param $message */ public function setMessage($message){ $this->message = $message; } /** * @return int */ public function getType(){ return $this->type; } } player = $player; $this->isFlying = (bool) $isFlying; } /** * @return bool */ public function isFlying(){ return $this->isFlying; } }player = $player; $this->isGliding = (bool) $isGliding; } /** * @return bool */ public function isGliding(){ return $this->isGliding; } /** * @return EventName|string */ public function getName(){ return "PlayerToggleGlideEvent"; } } player = $player; $this->isSneaking = (bool) $isSneaking; } /** * @return bool */ public function isSneaking(){ return $this->isSneaking; } }player = $player; $this->isSprinting = (bool) $isSprinting; } /** * @return bool */ public function isSprinting(){ return $this->isSprinting; } }player = $player; $this->action = $action; } /** * @return int */ public function getAction() : int{ return $this->action; } }attemptedPosition = $attemptedPosition; $this->player = $player; } /** * @return Vector3 */ public function getAttemptedPosition() : Vector3{ return $this->attemptedPosition; } }plugin = $plugin; } /** * @return Plugin */ public function getPlugin(){ return $this->plugin; } } packet = $packet; $this->player = $player; } /** * @return DataPacket */ public function getPacket(){ return $this->packet; } /** * @return Player */ public function getPlayer(){ return $this->player; } }packet = $packet; $this->player = $player; } /** * @return DataPacket */ public function getPacket(){ return $this->packet; } /** * @return Player */ public function getPlayer(){ return $this->player; } }memory = $memory; $this->memoryLimit = $memoryLimit; $this->global = (bool) $isGlobal; $this->triggerCount = (int) $triggerCount; } /** * Returns the memory usage at the time of the event call (in bytes) * * @return int */ public function getMemory(){ return $this->memory; } /** * Returns the memory limit defined (in bytes) * * @return int */ public function getMemoryLimit(){ return $this->memory; } /** * Returns the times this event has been called in the current low-memory state * * @return int */ public function getTriggerCount(){ return $this->triggerCount; } /** * @return bool */ public function isGlobal(){ return $this->global; } /** * Amount of memory already freed * * @return int */ public function getMemoryFreed(){ return $this->getMemory() - ($this->isGlobal() ? Utils::getMemoryUsage(true)[1] : Utils::getMemoryUsage(true)[0]); } }timeout = $timeout; $this->serverName = $server->getMotd(); $this->listPlugins = $server->getProperty("settings.query-plugins", true); $this->plugins = $server->getPluginManager()->getPlugins(); $this->players = []; foreach($server->getOnlinePlayers() as $player){ if($player->isOnline()){ $this->players[] = $player; } } if($server->isDServerEnabled() and $server->dserverConfig["queryMaxPlayers"]) $pc = $server->dserverConfig["queryMaxPlayers"]; elseif($server->isDServerEnabled() and $server->dserverConfig["queryAllPlayers"]) $pc = $server->getDServerMaxPlayers(); else $pc = $server->getMaxPlayers(); if($server->isDServerEnabled() and $server->dserverConfig["queryPlayers"]) $poc = $server->getDServerOnlinePlayers(); else $poc = count($this->players); $this->gametype = ($server->getGamemode() & 0x01) === 0 ? "SMP" : "CMP"; $this->version = $server->getVersion(); $this->server_engine = $server->getName() . " " . $server->getPocketMineVersion(); $this->map = $server->getDefaultLevel() === null ? "unknown" : $server->getDefaultLevel()->getName(); $this->numPlayers = $poc; $this->maxPlayers = $pc; $this->whitelist = $server->hasWhitelist() ? "on" : "off"; $this->port = $server->getPort(); $this->ip = $server->getIp(); } /** * Gets the min. timeout for Query Regeneration * * @return int */ public function getTimeout(){ return $this->timeout; } /** * @param $timeout */ public function setTimeout($timeout){ $this->timeout = $timeout; } /** * @return string */ public function getServerName(){ return $this->serverName; } /** * @param $serverName */ public function setServerName($serverName){ $this->serverName = $serverName; } /** * @return mixed */ public function canListPlugins(){ return $this->listPlugins; } /** * @param $value */ public function setListPlugins($value){ $this->listPlugins = (bool) $value; } /** * @return \pocketmine\plugin\Plugin[] */ public function getPlugins(){ return $this->plugins; } /** * @param \pocketmine\plugin\Plugin[] $plugins */ public function setPlugins(array $plugins){ $this->plugins = $plugins; } /** * @return \pocketmine\Player[] */ public function getPlayerList(){ return $this->players; } /** * @param \pocketmine\Player[] $players */ public function setPlayerList(array $players){ $this->players = $players; } /** * @return int */ public function getPlayerCount(){ return $this->numPlayers; } /** * @param $count */ public function setPlayerCount($count){ $this->numPlayers = (int) $count; } /** * @return int */ public function getMaxPlayerCount(){ return $this->maxPlayers; } /** * @param $count */ public function setMaxPlayerCount($count){ $this->maxPlayers = (int) $count; } /** * @return string */ public function getWorld(){ return $this->map; } /** * @param $world */ public function setWorld($world){ $this->map = (string) $world; } /** * Returns the extra Query data in key => value form * * @return array */ public function getExtraData(){ return $this->extraData; } /** * @param array $extraData */ public function setExtraData(array $extraData){ $this->extraData = $extraData; } /** * @return string */ public function getLongQuery(){ $query = ""; $plist = $this->server_engine; if(count($this->plugins) > 0 and $this->listPlugins){ $plist .= ":"; foreach($this->plugins as $p){ $d = $p->getDescription(); $plist .= " " . str_replace([";", ":", " "], ["", "", "_"], $d->getName()) . " " . str_replace([";", ":", " "], ["", "", "_"], $d->getVersion()) . ";"; } $plist = substr($plist, 0, -1); } $KVdata = [ "splitnum" => chr(128), "hostname" => $this->serverName, "gametype" => $this->gametype, "game_id" => self::GAME_ID, "version" => $this->version, "server_engine" => $this->server_engine, "plugins" => $plist, "map" => $this->map, "numplayers" => $this->numPlayers, "maxplayers" => $this->maxPlayers, "whitelist" => $this->whitelist, "hostip" => $this->ip, "hostport" => $this->port ]; foreach($KVdata as $key => $value){ $query .= $key . "\x00" . $value . "\x00"; } foreach($this->extraData as $key => $value){ $query .= $key . "\x00" . $value . "\x00"; } $query .= "\x00\x01player_\x00\x00"; foreach($this->players as $player){ $query .= $player->getName() . "\x00"; } $query .= "\x00"; return $query; } /** * @return string */ public function getShortQuery(){ return $this->serverName . "\x00" . $this->gametype . "\x00" . $this->map . "\x00" . $this->numPlayers . "\x00" . $this->maxPlayers . "\x00" . Binary::writeLShort($this->port) . $this->ip . "\x00"; } }sender = $sender; $this->command = $command; } /** * @return CommandSender */ public function getSender(){ return $this->sender; } /** * @return string */ public function getCommand(){ return $this->command; } /** * @param string $command */ public function setCommand($command){ $this->command = $command; } }holder; } /** * @return int */ public function getResultSlotIndex(){ return self::RESULT; } /** * @param Player $player * @param Item $resultItem * * @return bool */ public function onRename(Player $player, Item $resultItem) : bool{ if(!$resultItem->equals($this->getItem(self::TARGET), true, false, true)){ //Item does not match target item. Everything must match except the tags. return false; } if($player->getXpLevel() < $resultItem->getRepairCost()){ //Not enough exp return false; } $player->takeXpLevel($resultItem->getRepairCost()); $this->clearAll(); if(!$player->getServer()->allowInventoryCheats and !$player->isCreative()){ if(!$player->getFloatingInventory()->canAddItem($resultItem)){ return false; } $player->getFloatingInventory()->addItem($resultItem); } return true; } /** * @param Player $player * @param Item $target * @param Item $sacrifice * * @return bool */ public function process(Player $player, Item $target, Item $sacrifice){ $resultItem = clone $target; Server::getInstance()->getPluginManager()->callEvent($ev = new AnvilProcessEvent($this)); if($ev->isCancelled()){ $this->clearAll(); return false; } if($sacrifice instanceof EnchantedBook && $sacrifice->hasEnchantments()){ //Enchanted Books! foreach($sacrifice->getEnchantments() as $enchant){ $resultItem->addEnchantment($enchant); } if($player->getXpLevel() < $resultItem->getRepairCost()){ //Not enough exp return false; } $player->takeXpLevel($resultItem->getRepairCost()); $this->clearAll(); if(!$player->getServer()->allowInventoryCheats and !$player->isCreative()){ if(!$player->getFloatingInventory()->canAddItem($resultItem)){ return false; } $player->getFloatingInventory()->addItem($resultItem); } } } /** * @param Transaction $transaction * * @return bool */ public function processSlotChange(Transaction $transaction) : bool{ if($transaction->getSlot() === $this->getResultSlotIndex()){ return false; } return true; } /** * @param int $index * @param Item $before * @param bool $send */ public function onSlotChange($index, $before, $send){ //Do not send anvil slot updates to anyone. This will cause a client crash. } /** * @param Player $who */ public function onClose(Player $who){ parent::onClose($who); $this->getHolder()->getLevel()->dropItem($this->getHolder()->add(0.5, 0.5, 0.5), $this->getItem(0)); $this->getHolder()->getLevel()->dropItem($this->getHolder()->add(0.5, 0.5, 0.5), $this->getItem(1)); $this->clear(0); $this->clear(1); $this->clear(2); } } holder = $holder; $this->type = $type; if($overrideSize !== null){ $this->size = (int) $overrideSize; }else{ $this->size = $this->type->getDefaultSize(); } if($overrideTitle !== null){ $this->title = $overrideTitle; }else{ $this->title = $this->type->getDefaultTitle(); } $this->name = $this->type->getDefaultTitle(); $this->setContents($items); } public function __destruct(){ $this->holder = null; $this->slots = []; } /** * @return int */ public function getSize(){ return $this->size; } /** * @return int */ public function getHotbarSize(){ return 0; } /** * @param $size */ public function setSize($size){ $this->size = (int) $size; } /** * @return int */ public function getMaxStackSize(){ return $this->maxStackSize; } /** * @return string */ public function getName() : string{ return $this->name; } /** * @return string */ public function getTitle(){ return $this->title; } /** * @param int $index * * @return Item */ public function getItem($index){ return isset($this->slots[$index]) ? clone $this->slots[$index] : Item::get(Item::AIR, 0, 0); } /** * @return Item[] */ public function getContents(){ return $this->slots; } /** * @param Item[] $items * @param bool $send */ public function setContents(array $items, $send = true){ if(count($items) > $this->size){ $items = array_slice($items, 0, $this->size, true); } for($i = 0; $i < $this->size; ++$i){ if(!isset($items[$i])){ if(isset($this->slots[$i])){ $this->clear($i, $send); } }else{ if(!$this->setItem($i, $items[$i], $send)){ $this->clear($i, $send); } } } } /** * @param int $index * @param Item $item * @param bool $send * * @return bool */ public function setItem($index, Item $item, $send = true){ $item = clone $item; if($index < 0 or $index >= $this->size){ return false; }elseif($item->getId() === 0 or $item->getCount() <= 0){ return $this->clear($index, $send); } $holder = $this->getHolder(); if($holder instanceof Entity){ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($holder, $this->getItem($index), $item, $index)); if($ev->isCancelled()){ $this->sendSlot($index, $this->getViewers()); return false; } $item = $ev->getNewItem(); } $old = $this->getItem($index); $this->slots[$index] = clone $item; $this->onSlotChange($index, $old, $send); return true; } /** * @param Item $item * * @return bool */ public function contains(Item $item){ $count = max(1, $item->getCount()); $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasCompoundTag(); foreach($this->getContents() as $i){ if($item->equals($i, $checkDamage, $checkTags)){ $count -= $i->getCount(); if($count <= 0){ return true; } } } return false; } /** * @param $slot * @param Item $item * @param bool $matchCount * * @return bool */ public function slotContains($slot, Item $item, $matchCount = false){ if($matchCount){ return $this->getItem($slot)->equals($item, true, true, true); }else{ return $this->getItem($slot)->equals($item) and $this->getItem($slot)->getCount() >= $item->getCount(); } } /** * @param Item $item * * @return array */ public function all(Item $item){ $slots = []; $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasCompoundTag(); foreach($this->getContents() as $index => $i){ if($item->equals($i, $checkDamage, $checkTags)){ $slots[$index] = $i; } } return $slots; } /** * @param Item $item * @param bool $send */ public function remove(Item $item, $send = true){ $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasCompoundTag(); $checkCount = $item->getCount() === null ? false : true; foreach($this->getContents() as $index => $i){ if($item->equals($i, $checkDamage, $checkTags, $checkCount)){ $this->clear($index, $send); break; } } } /** * @param Item $item * * @return int|string */ public function first(Item $item){ $count = max(1, $item->getCount()); $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasCompoundTag(); foreach($this->getContents() as $index => $i){ if($item->equals($i, $checkDamage, $checkTags) and $i->getCount() >= $count){ return $index; } } return -1; } /** * @return int */ public function firstEmpty(){ for($i = 0; $i < $this->size; ++$i){ if($this->getItem($i)->getId() === Item::AIR){ return $i; } } return -1; } /** * @return int */ public function firstOccupied(){ for($i = 0; $i < $this->size; $i++){ if(($item = $this->getItem($i))->getId() !== Item::AIR and $item->getCount() > 0){ return $i; } } return -1; } /** * @param Item $item * * @return bool */ public function canAddItem(Item $item){ $item = clone $item; $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasCompoundTag(); for($i = 0; $i < $this->getSize(); ++$i){ $slot = $this->getItem($i); if($item->equals($slot, $checkDamage, $checkTags)){ if(($diff = $slot->getMaxStackSize() - $slot->getCount()) > 0){ $item->setCount($item->getCount() - $diff); } }elseif($slot->getId() === Item::AIR){ $item->setCount($item->getCount() - $this->getMaxStackSize()); } if($item->getCount() <= 0){ return true; } } return false; } /** * @param array ...$slots * * @return Item[] */ public function addItem(...$slots){ /** @var Item[] $itemSlots */ /** @var Item[] $slots */ $itemSlots = []; foreach($slots as $slot){ if(!($slot instanceof Item)){ throw new \InvalidArgumentException("Expected Item, got " . gettype($slot)); } if($slot->getId() !== 0 and $slot->getCount() > 0){ $itemSlots[] = clone $slot; } } $emptySlots = []; for($i = 0; $i < $this->getSize(); ++$i){ $item = $this->getItem($i); if($item->getId() === Item::AIR or $item->getCount() <= 0){ $emptySlots[] = $i; } foreach($itemSlots as $index => $slot){ if($slot->equals($item) and $item->getCount() < $item->getMaxStackSize()){ $amount = min($item->getMaxStackSize() - $item->getCount(), $slot->getCount(), $this->getMaxStackSize()); if($amount > 0){ $slot->setCount($slot->getCount() - $amount); $item->setCount($item->getCount() + $amount); $this->setItem($i, $item); if($slot->getCount() <= 0){ unset($itemSlots[$index]); } } } } if(count($itemSlots) === 0){ break; } } if(count($itemSlots) > 0 and count($emptySlots) > 0){ foreach($emptySlots as $slotIndex){ //This loop only gets the first item, then goes to the next empty slot foreach($itemSlots as $index => $slot){ $amount = min($slot->getMaxStackSize(), $slot->getCount(), $this->getMaxStackSize()); $slot->setCount($slot->getCount() - $amount); $item = clone $slot; $item->setCount($amount); $this->setItem($slotIndex, $item); if($slot->getCount() <= 0){ unset($itemSlots[$index]); } break; } } } return $itemSlots; } /** * @param array ...$slots * * @return Item[] */ public function removeItem(...$slots){ /** @var Item[] $itemSlots */ /** @var Item[] $slots */ $itemSlots = []; foreach($slots as $slot){ if(!($slot instanceof Item)){ throw new \InvalidArgumentException("Expected Item[], got " . gettype($slot)); } if($slot->getId() !== 0 and $slot->getCount() > 0){ $itemSlots[] = clone $slot; } } for($i = 0; $i < $this->getSize(); ++$i){ $item = $this->getItem($i); if($item->getId() === Item::AIR or $item->getCount() <= 0){ continue; } foreach($itemSlots as $index => $slot){ if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasCompoundTag())){ $amount = min($item->getCount(), $slot->getCount()); $slot->setCount($slot->getCount() - $amount); $item->setCount($item->getCount() - $amount); $this->setItem($i, $item); if($slot->getCount() <= 0){ unset($itemSlots[$index]); } } } if(count($itemSlots) === 0){ break; } } return $itemSlots; } /** * @param int $index * @param bool $send * * @return bool */ public function clear($index, $send = true){ if(isset($this->slots[$index])){ $item = Item::get(Item::AIR, 0, 0); $old = $this->slots[$index]; $holder = $this->getHolder(); if($holder instanceof Entity){ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($holder, $old, $item, $index)); if($ev->isCancelled()){ $this->sendSlot($index, $this->getViewers()); return false; } $item = $ev->getNewItem(); } if($item->getId() !== Item::AIR){ $this->slots[$index] = clone $item; }else{ unset($this->slots[$index]); } $this->onSlotChange($index, $old, $send); } return true; } /** * @param bool $send */ public function clearAll($send = true){ foreach($this->getContents() as $index => $i){ $this->clear($index, $send); } } /** * @return Player[] */ public function getViewers(){ return $this->viewers; } /** * @return InventoryHolder */ public function getHolder(){ return $this->holder; } /** * @param int $size */ public function setMaxStackSize($size){ $this->maxStackSize = (int) $size; } /** * @param Player $who * * @return bool */ public function open(Player $who){ $who->getServer()->getPluginManager()->callEvent($ev = new InventoryOpenEvent($this, $who)); if($ev->isCancelled()){ return false; } $this->onOpen($who); return true; } /** * @param Player $who * * @return mixed|void */ public function close(Player $who){ $this->onClose($who); } /** * @param Player $who */ public function onOpen(Player $who){ $this->viewers[spl_object_hash($who)] = $who; } /** * @param Player $who */ public function onClose(Player $who){ unset($this->viewers[spl_object_hash($who)]); } /** * @param int $index * @param Item $before * @param bool $send */ public function onSlotChange($index, $before, $send){ if($send){ $this->sendSlot($index, $this->getViewers()); } } /** * @param Transaction $transaction * * @return bool */ public function processSlotChange(Transaction $transaction) : bool{ return true; } /** * @param Player|Player[] $target */ public function sendContents($target){ if($target instanceof Player){ $target = [$target]; } $pk = new ContainerSetContentPacket(); $pk->slots = []; for($i = 0; $i < $this->getSize(); ++$i){ $pk->slots[$i] = $this->getItem($i); } foreach($target as $player){ if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){ $this->close($player); continue; } $pk->windowid = $id; $pk->targetEid = $player->getId(); $player->dataPacket($pk); } } /** * @param int $index * @param Player|Player[] $target */ public function sendSlot($index, $target){ if($target instanceof Player){ $target = [$target]; } $pk = new ContainerSetSlotPacket(); $pk->slot = $index; $pk->item = clone $this->getItem($index); foreach($target as $player){ if(($id = $player->getWindowId($this)) === -1){ $this->close($player); continue; } $pk->windowid = $id; $player->dataPacket($pk); } } /** * @return InventoryType */ public function getType(){ return $this->type; } }inventory = $inventory; $this->slot = (int) $slot; $this->targetItem = clone $targetItem; $this->creationTime = microtime(true); $this->transactionType = $transactionType; $this->achievements = $achievements; } /** * @return float|mixed */ public function getCreationTime(){ return $this->creationTime; } /** * @return Inventory */ public function getInventory(){ return $this->inventory; } /** * @return int */ public function getSlot(){ return $this->slot; } /** * @return Item */ public function getTargetItem(){ return clone $this->targetItem; } /** * @return Item */ public function getSourceItem(){ return clone $this->inventory->getItem($this->slot); } /** * @param Item $item */ public function setTargetItem(Item $item){ $this->targetItem = clone $item; } /** * @return int */ public function getFailures(){ return $this->failures; } public function addFailure(){ $this->failures++; } /** * @return bool */ public function succeeded(){ return $this->wasSuccessful; } /** * @param bool $value */ public function setSuccess($value = true){ $this->wasSuccessful = $value; } /** * @return int */ public function getTransactionType(){ return $this->transactionType; } /** * @return array|string|\string[] */ public function getAchievements(){ return $this->achievements; } /** * @return bool */ public function hasAchievements(){ return count($this->achievements) !== 0; } /** * @param string $achievementName */ public function addAchievement(string $achievementName){ $this->achievements[] = $achievementName; } /** * @param Player $source * * Sends a slot update to inventory viewers * For successful transactions, update non-source viewers (source does not need updating) * For failed transactions, update the source (non-source viewers will see nothing anyway) */ public function sendSlotUpdate(Player $source){ if($this->getInventory() instanceof TemporaryInventory){ return; } if($this->wasSuccessful){ $targets = $this->getInventory()->getViewers(); unset($targets[spl_object_hash($source)]); }else{ $targets = [$source]; } $this->inventory->sendSlot($this->slot, $targets); } /** * Returns the change in inventory resulting from this transaction * * @return array ("in" => items added to the inventory, "out" => items removed from the inventory) * ] */ public function getChange(){ $sourceItem = $this->getInventory()->getItem($this->slot); if($sourceItem->equals($this->targetItem, true, true, true)){ //This should never happen, somehow a change happened where nothing changed return null; }elseif($sourceItem->equals($this->targetItem)){ //Same item, change of count $item = clone $sourceItem; $countDiff = $this->targetItem->getCount() - $sourceItem->getCount(); $item->setCount(abs($countDiff)); if($countDiff < 0){ //Count decreased return ["in" => null, "out" => $item]; }elseif($countDiff > 0){ //Count increased return [ "in" => $item, "out" => null]; }else{ //Should be impossible (identical items and no count change) //This should be caught by the first condition even if it was possible return null; } }elseif($sourceItem->getId() !== Item::AIR and $this->targetItem->getId() === Item::AIR){ //Slot emptied (item removed) return ["in" => null, "out" => clone $sourceItem]; }elseif($sourceItem->getId() === Item::AIR and $this->targetItem->getId() !== Item::AIR){ //Slot filled (item added) return ["in" => $this->getTargetItem(), "out" => null]; }else{ //Some other slot change - an item swap (tool damage changes will be ignored as they are processed server-side before any change is sent by the client return ["in" => $this->getTargetItem(), "out" => clone $sourceItem]; } } /** * @param Player $source * * @return bool * * Handles transaction execution. Returns whether transaction was successful or not. */ public function execute(Player $source) : bool{ if($this->getInventory()->processSlotChange($this)){ //This means that the transaction should be handled the normal way if(!$source->getServer()->allowInventoryCheats and !$source->isCreative()){ $change = $this->getChange(); if($change === null){ //No changes to make, ignore this transaction return true; } /* Verify that we have the required items */ if($change["out"] instanceof Item){ if(!$this->getInventory()->slotContains($this->getSlot(), $change["out"])){ return false; } } if($change["in"] instanceof Item){ if(!$source->getFloatingInventory()->contains($change["in"])){ return false; } } /* All checks passed, make changes to floating inventory * This will not be reached unless all requirements are met */ if($change["out"] instanceof Item){ $source->getFloatingInventory()->addItem($change["out"]); } if($change["in"] instanceof Item){ $source->getFloatingInventory()->removeItem($change["in"]); } } $this->getInventory()->setItem($this->getSlot(), $this->getTargetItem(), false); } /* Process transaction achievements, like getting iron from a furnace */ foreach($this->achievements as $achievement){ $source->awardAchievement($achievement); } return true; } } holder; } }holder; } /** * @param Item $item */ public function setIngredient(Item $item){ $this->setItem(0, $item); } /** * @return Item */ public function getIngredient(){ return $this->getItem(0); } /** * @param int $index * @param Item $before * @param bool $send */ public function onSlotChange($index, $before, $send){ parent::onSlotChange($index, $before, $send); $this->getHolder()->scheduleUpdate(); $this->getHolder()->updateSurface(); } }output = clone $result; $this->ingredient = clone $ingredient; $this->potion = clone $potion; } /** * @return Item */ public function getPotion(){ return clone $this->potion; } /** * @return null */ public function getId(){ return $this->id; } /** * @param UUID $id */ public function setId(UUID $id){ if($this->id !== null){ throw new \InvalidStateException("Id is already set"); } $this->id = $id; } /** * @param Item $item */ public function setInput(Item $item){ $this->ingredient = clone $item; } /** * @return Item */ public function getInput(){ return clone $this->ingredient; } /** * @return Item */ public function getResult(){ return clone $this->output; } public function registerToCraftingManager(){ Server::getInstance()->getCraftingManager()->registerBrewingRecipe($this); } }holder; } /** * @param bool $withAir * * @return array|\pocketmine\item\Item[] */ public function getContents($withAir = false){ if($withAir){ $contents = []; for($i = 0; $i < $this->getSize(); ++$i){ $contents[$i] = $this->getItem($i); } return $contents; } return parent::getContents(); } /** * @param Player $who */ public function onOpen(Player $who){ parent::onOpen($who); if(count($this->getViewers()) === 1){ $pk = new BlockEventPacket(); $pk->x = $this->getHolder()->getX(); $pk->y = $this->getHolder()->getY(); $pk->z = $this->getHolder()->getZ(); $pk->case1 = 1; $pk->case2 = 2; if(($level = $this->getHolder()->getLevel()) instanceof Level){ $level->broadcastLevelSoundEvent($this->getHolder(), LevelSoundEventPacket::SOUND_CHEST_OPEN); $level->addChunkPacket($this->getHolder()->getX() >> 4, $this->getHolder()->getZ() >> 4, $pk); } } if($this->getHolder()->getLevel() instanceof Level){ /** @var TrappedChest $block */ $block = $this->getHolder()->getBlock(); if($block instanceof TrappedChest){ if(!$block->isActivated()){ $block->activate(); } } } } /** * @param Player $who */ public function onClose(Player $who){ if($this->getHolder()->getLevel() instanceof Level){ /** @var TrappedChest $block */ $block = $this->getHolder()->getBlock(); if($block instanceof TrappedChest){ if($block->isActivated()){ $block->deactivate(); } } } if(count($this->getViewers()) === 1){ $pk = new BlockEventPacket(); $pk->x = $this->getHolder()->getX(); $pk->y = $this->getHolder()->getY(); $pk->z = $this->getHolder()->getZ(); $pk->case1 = 1; $pk->case2 = 0; if(($level = $this->getHolder()->getLevel()) instanceof Level){ $level->broadcastLevelSoundEvent($this->getHolder(), LevelSoundEventPacket::SOUND_CHEST_CLOSED); $level->addChunkPacket($this->getHolder()->getX() >> 4, $this->getHolder()->getZ() >> 4, $pk); } } parent::onClose($who); } }windowid = $who->getWindowId($this); $pk->type = $this->getType()->getNetworkType(); $holder = $this->getHolder(); if($holder instanceof Vector3){ $pk->x = $holder->getX(); $pk->y = $holder->getY(); $pk->z = $holder->getZ(); }else{ $pk->x = $pk->y = $pk->z = 0; } $who->dataPacket($pk); $this->sendContents($who); } /** * @param Player $who */ public function onClose(Player $who){ $pk = new ContainerClosePacket(); $pk->windowid = $who->getWindowId($this); $who->dataPacket($pk); parent::onClose($who); } }getDefaultTitle() !== "Crafting"){ throw new \InvalidStateException("Invalid Inventory type, expected CRAFTING or WORKBENCH"); } $this->resultInventory = $resultInventory; parent::__construct($holder, $inventoryType); } /** * @return Inventory */ public function getResultInventory(){ return $this->resultInventory; } /** * @return mixed */ public function getSize(){ return $this->getResultInventory()->getSize() + parent::getSize(); } }registerBrewingStand(); // load recipes from src/pocketmine/resources/recipes.json $recipes = new Config(Server::getInstance()->getFilePath() . "src/pocketmine/resources/recipes.json", Config::JSON, []); MainLogger::getLogger()->info("Loading recipes..."); foreach($recipes->getAll() as $recipe){ switch($recipe["type"]){ case 0: // TODO: handle multiple result items $first = $recipe["output"][0]; $result = new ShapelessRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"])); foreach($recipe["input"] as $ingredient){ $result->addIngredient(Item::get($ingredient["id"], $ingredient["damage"], $ingredient["count"], $first["nbt"])); } $this->registerRecipe($result); break; case 1: // TODO: handle multiple result items $first = $recipe["output"][0]; $result = new ShapedRecipe(Item::get($first["id"], $first["damage"], $first["count"], $first["nbt"]), $recipe["height"], $recipe["width"]); $shape = array_chunk($recipe["input"], $recipe["width"]); foreach($shape as $y => $row){ foreach($row as $x => $ingredient){ $result->addIngredient($x, $y, Item::get($ingredient["id"], ($ingredient["damage"] < 0 ? -1 : $ingredient["damage"]), $ingredient["count"], $ingredient["nbt"])); } } $this->registerRecipe($result); break; case 2: case 3: $result = $recipe["output"]; $resultItem = Item::get($result["id"], $result["damage"], $result["count"], $result["nbt"]); $this->registerRecipe(new FurnaceRecipe($resultItem, Item::get($recipe["inputId"], $recipe["inputDamage"] ?? -1, 1))); break; default: break; } } $this->buildCraftingDataCache(); } /** * Rebuilds the cached CraftingDataPacket. */ public function buildCraftingDataCache(){ Timings::$craftingDataCacheRebuildTimer->startTiming(); $pk = new CraftingDataPacket(); $pk->cleanRecipes = true; foreach($this->recipes as $recipe){ if($recipe instanceof ShapedRecipe){ $pk->addShapedRecipe($recipe); }elseif($recipe instanceof ShapelessRecipe){ $pk->addShapelessRecipe($recipe); } } foreach($this->furnaceRecipes as $recipe){ $pk->addFurnaceRecipe($recipe); } $pk->encode(); $pk->isEncoded = true; $this->craftingDataCache = $pk; Timings::$craftingDataCacheRebuildTimer->stopTiming(); } /** * Returns a CraftingDataPacket for sending to players. Rebuilds the cache if it is outdated. * * @return CraftingDataPacket */ public function getCraftingDataPacket() : CraftingDataPacket{ if($this->craftingDataCache === null){ $this->buildCraftingDataCache(); } return $this->craftingDataCache; } protected function registerBrewingStand(){ //Potion //WATER_BOTTLE $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::AWKWARD, 1), Item::get(Item::NETHER_WART, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::THICK, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE_EXTENDED, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::WEAKNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE, 1), Item::get(Item::GHAST_TEAR, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE, 1), Item::get(Item::GLISTERING_MELON, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE, 1), Item::get(Item::BLAZE_POWDER, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE, 1), Item::get(Item::MAGMA_CREAM, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE, 1), Item::get(Item::SUGAR, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE, 1), Item::get(Item::SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::MUNDANE, 1), Item::get(Item::RABBIT_FOOT, 0, 1), Item::get(Item::POTION, Potion::WATER_BOTTLE, 1))); //To WEAKNESS $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::WEAKNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::MUNDANE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::WEAKNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::THICK, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::WEAKNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::MUNDANE_EXTENDED, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::WEAKNESS_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::WEAKNESS, 1))); //GHAST_TEAR and BLAZE_POWDER $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::REGENERATION, 1), Item::get(Item::GHAST_TEAR, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::REGENERATION_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::REGENERATION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::REGENERATION_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::REGENERATION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::STRENGTH, 1), Item::get(Item::BLAZE_POWDER, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::STRENGTH_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::STRENGTH, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::STRENGTH_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::STRENGTH, 1))); //SPIDER_EYE GLISTERING_MELON and PUFFERFISH $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::POISON, 1), Item::get(Item::SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::POISON_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::POISON, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::POISON_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::POISON, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HEALING, 1), Item::get(Item::GLISTERING_MELON, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HEALING_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::HEALING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::WATER_BREATHING, 1), Item::get(Item::PUFFER_FISH, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::WATER_BREATHING_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::WATER_BREATHING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HARMING, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::WATER_BREATHING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HARMING, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::HEALING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HARMING, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::POISON, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HARMING_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::HARMING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HARMING_TWO, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::HEALING_TWO, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::HARMING_TWO, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::POISON_T, 1))); //SUGAR MAGMA_CREAM and RABBIT_FOOT $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SWIFTNESS, 1), Item::get(Item::SUGAR, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SWIFTNESS_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::SWIFTNESS, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SWIFTNESS_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::SWIFTNESS, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::FIRE_RESISTANCE, 1), Item::get(Item::MAGMA_CREAM, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::FIRE_RESISTANCE_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::FIRE_RESISTANCE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::LEAPING, 1), Item::get(Item::RABBIT_FOOT, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::LEAPING_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::LEAPING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::LEAPING_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::LEAPING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SLOWNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::FIRE_RESISTANCE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SLOWNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::SWIFTNESS, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SLOWNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::LEAPING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SLOWNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::FIRE_RESISTANCE_T, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SLOWNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::LEAPING_T, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SLOWNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::SWIFTNESS_T, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::SLOWNESS_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::SLOWNESS, 1))); //GOLDEN_CARROT $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::NIGHT_VISION, 1), Item::get(Item::GOLDEN_CARROT, 0, 1), Item::get(Item::POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::NIGHT_VISION_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::NIGHT_VISION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::INVISIBILITY, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::NIGHT_VISION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::INVISIBILITY_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::POTION, Potion::INVISIBILITY, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::POTION, Potion::INVISIBILITY_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::POTION, Potion::NIGHT_VISION_T, 1))); //===================================================================分隔符======================================================================= //SPLASH_POTION //WATER_BOTTLE $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1), Item::get(Item::NETHER_WART, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::THICK, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE_EXTENDED, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::WEAKNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1), Item::get(Item::GHAST_TEAR, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1), Item::get(Item::GLISTERING_MELON, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1), Item::get(Item::BLAZE_POWDER, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1), Item::get(Item::MAGMA_CREAM, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1), Item::get(Item::SUGAR, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1), Item::get(Item::SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1), Item::get(Item::RABBIT_FOOT, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BOTTLE, 1))); //To WEAKNESS $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::WEAKNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::MUNDANE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::WEAKNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::THICK, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::WEAKNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::MUNDANE_EXTENDED, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::WEAKNESS_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WEAKNESS, 1))); //GHAST_TEAR and BLAZE_POWDER $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::REGENERATION, 1), Item::get(Item::GHAST_TEAR, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::REGENERATION_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::REGENERATION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::REGENERATION_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::REGENERATION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::STRENGTH, 1), Item::get(Item::BLAZE_POWDER, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::STRENGTH_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::STRENGTH, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::STRENGTH_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::STRENGTH, 1))); //SPIDER_EYE GLISTERING_MELON and PUFFERFISH $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::POISON, 1), Item::get(Item::SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::POISON_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::POISON, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::POISON_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::POISON, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HEALING, 1), Item::get(Item::GLISTERING_MELON, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HEALING_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::HEALING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::WATER_BREATHING, 1), Item::get(Item::PUFFER_FISH, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::WATER_BREATHING_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BREATHING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HARMING, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::WATER_BREATHING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HARMING, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::HEALING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HARMING, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::POISON, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HARMING_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::HARMING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HARMING_TWO, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::HEALING_TWO, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::HARMING_TWO, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::POISON_T, 1))); //SUGAR MAGMA_CREAM and RABBIT_FOOT $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SWIFTNESS, 1), Item::get(Item::SUGAR, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SWIFTNESS_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::SWIFTNESS, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SWIFTNESS_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::SWIFTNESS, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::FIRE_RESISTANCE, 1), Item::get(Item::MAGMA_CREAM, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::FIRE_RESISTANCE_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::FIRE_RESISTANCE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::LEAPING, 1), Item::get(Item::RABBIT_FOOT, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::LEAPING_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::LEAPING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::LEAPING_TWO, 1), Item::get(Item::GLOWSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::LEAPING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SLOWNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::FIRE_RESISTANCE, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SLOWNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::SWIFTNESS, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SLOWNESS, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::LEAPING, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SLOWNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::FIRE_RESISTANCE_T, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SLOWNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::LEAPING_T, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SLOWNESS_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::SWIFTNESS_T, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::SLOWNESS_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::SLOWNESS, 1))); //GOLDEN_CARROT $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::NIGHT_VISION, 1), Item::get(Item::GOLDEN_CARROT, 0, 1), Item::get(Item::SPLASH_POTION, Potion::AWKWARD, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::NIGHT_VISION_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::NIGHT_VISION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::INVISIBILITY, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::NIGHT_VISION, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::INVISIBILITY_T, 1), Item::get(Item::REDSTONE_DUST, 0, 1), Item::get(Item::SPLASH_POTION, Potion::INVISIBILITY, 1))); $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, Potion::INVISIBILITY_T, 1), Item::get(Item::FERMENTED_SPIDER_EYE, 0, 1), Item::get(Item::SPLASH_POTION, Potion::NIGHT_VISION_T, 1))); //===================================================================分隔符======================================================================= //普通药水升级成喷溅 foreach(Potion::POTIONS as $potion => $effect){ $this->registerBrewingRecipe(new BrewingRecipe(Item::get(Item::SPLASH_POTION, $potion, 1), Item::get(Item::GUNPOWDER, 0, 1), Item::get(Item::POTION, $potion, 1))); } } /** * @param Item $i1 * @param Item $i2 * * @return int */ public function sort(Item $i1, Item $i2){ if($i1->getId() > $i2->getId()){ return 1; }elseif($i1->getId() < $i2->getId()){ return -1; }elseif($i1->getDamage() > $i2->getDamage()){ return 1; }elseif($i1->getDamage() < $i2->getDamage()){ return -1; }elseif($i1->getCount() > $i2->getCount()){ return 1; }elseif($i1->getCount() < $i2->getCount()){ return -1; }else{ return 0; } } /** * @param UUID $id * * @return Recipe */ public function getRecipe(UUID $id){ $index = $id->toBinary(); return $this->recipes[$index] ?? null; } /** * @return Recipe[] */ public function getRecipes(){ return $this->recipes; } /** * @param Item $item * * @return array */ public function getRecipesByResult(Item $item){ return @array_values($this->recipeLookup[$item->getId() . ":" . $item->getDamage()]) ?? []; } /** * @return FurnaceRecipe[] */ public function getFurnaceRecipes(){ return $this->furnaceRecipes; } /** * @param Item $input * * @return FurnaceRecipe */ public function matchFurnaceRecipe(Item $input){ if(isset($this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()])){ return $this->furnaceRecipes[$input->getId() . ":" . $input->getDamage()]; }elseif(isset($this->furnaceRecipes[$input->getId() . ":?"])){ return $this->furnaceRecipes[$input->getId() . ":?"]; } return null; } /** * @param Item $input * @param Item $potion * * @return BrewingRecipe */ public function matchBrewingRecipe(Item $input, Item $potion){ $subscript = $input->getId() . ":" . ($input->getDamage() === null ? "0" : $input->getDamage()) . ":" . $potion->getId() . ":" . ($potion->getDamage() === null ? "0" : $potion->getDamage()); if(isset($this->brewingRecipes[$subscript])){ return $this->brewingRecipes[$subscript]; } return null; } /** * @param ShapedRecipe $recipe */ public function registerShapedRecipe(ShapedRecipe $recipe){ $result = $recipe->getResult(); $this->recipes[$recipe->getId()->toBinary()] = $recipe; $ingredients = $recipe->getIngredientMap(); $hash = ""; foreach($ingredients as $v){ foreach($v as $item){ if($item !== null){ /** @var Item $item */ $hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ","; } } $hash .= ";"; } $this->recipeLookup[$result->getId() . ":" . $result->getDamage()][$hash] = $recipe; $this->craftingDataCache = null; } /** * @param ShapelessRecipe $recipe */ public function registerShapelessRecipe(ShapelessRecipe $recipe){ $result = $recipe->getResult(); $this->recipes[$recipe->getId()->toBinary()] = $recipe; $hash = ""; $ingredients = $recipe->getIngredientList(); usort($ingredients, [$this, "sort"]); foreach($ingredients as $item){ $hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ","; } $this->recipeLookup[$result->getId() . ":" . $result->getDamage()][$hash] = $recipe; $this->craftingDataCache = null; } /** * @param FurnaceRecipe $recipe */ public function registerFurnaceRecipe(FurnaceRecipe $recipe){ $input = $recipe->getInput(); $this->furnaceRecipes[$input->getId() . ":" . ($input->hasAnyDamageValue() ? "?" : $input->getDamage())] = $recipe; $this->craftingDataCache = null; } /** * @param BrewingRecipe $recipe */ public function registerBrewingRecipe(BrewingRecipe $recipe){ $input = $recipe->getInput(); $potion = $recipe->getPotion(); $this->brewingRecipes[$input->getId() . ":" . ($input->getDamage() === null ? "0" : $input->getDamage()) . ":" . $potion->getId() . ":" . ($potion->getDamage() === null ? "0" : $potion->getDamage())] = $recipe; } /** * @param ShapelessRecipe $recipe * * @return bool */ public function matchRecipe(ShapelessRecipe $recipe){ if(!isset($this->recipeLookup[$idx = $recipe->getResult()->getId() . ":" . $recipe->getResult()->getDamage()])){ return false; } $hash = ""; $ingredients = $recipe->getIngredientList(); usort($ingredients, [$this, "sort"]); foreach($ingredients as $item){ $hash .= $item->getId() . ":" . ($item->hasAnyDamageValue() ? "?" : $item->getDamage()) . "x" . $item->getCount() . ","; } if(isset($this->recipeLookup[$idx][$hash])){ return true; } $hasRecipe = null; foreach($this->recipeLookup[$idx] as $recipe){ if($recipe instanceof ShapelessRecipe){ if($recipe->getIngredientCount() !== count($ingredients)){ continue; } $checkInput = $recipe->getIngredientList(); foreach($ingredients as $item){ $amount = $item->getCount(); foreach($checkInput as $k => $checkItem){ if($checkItem->equals($item, !$checkItem->hasAnyDamageValue(), $checkItem->hasCompoundTag())){ $remove = min($checkItem->getCount(), $amount); $checkItem->setCount($checkItem->getCount() - $remove); if($checkItem->getCount() === 0){ unset($checkInput[$k]); } $amount -= $remove; if($amount === 0){ break; } } } } if(count($checkInput) === 0){ $hasRecipe = $recipe; break; } } if($hasRecipe instanceof Recipe){ break; } } return $hasRecipe !== null; } /** * @param Recipe $recipe */ public function registerRecipe(Recipe $recipe){ $recipe->setId(UUID::fromData(++self::$RECIPE_COUNT, $recipe->getResult()->getId(), $recipe->getResult()->getDamage(), $recipe->getResult()->getCount(), $recipe->getResult()->getCompoundTag())); if($recipe instanceof ShapedRecipe){ $this->registerShapedRecipe($recipe); }elseif($recipe instanceof ShapelessRecipe){ $this->registerShapelessRecipe($recipe); }elseif($recipe instanceof FurnaceRecipe){ $this->registerFurnaceRecipe($recipe); } } } holder; } }left = $left->getRealInventory(); $this->right = $right->getRealInventory(); $items = array_merge($this->left->getContents(true), $this->right->getContents(true)); BaseInventory::__construct($this, InventoryType::get(InventoryType::DOUBLE_CHEST), $items); } /** * @return $this */ public function getInventory(){ return $this; } /** * @return Chest */ public function getHolder(){ return $this->left->getHolder(); } /** * @param int $index * * @return Item */ public function getItem($index){ return $index < $this->left->getSize() ? $this->left->getItem($index) : $this->right->getItem($index - $this->right->getSize()); } /** * @param int $index * @param Item $item * * @param bool $send * @return bool */ public function setItem($index, Item $item, $send = true){ return $index < $this->left->getSize() ? $this->left->setItem($index, $item) : $this->right->setItem($index - $this->right->getSize(), $item); } /** * @param int $index * * @param bool $send * @return bool */ public function clear($index, $send = true){ return $index < $this->left->getSize() ? $this->left->clear($index) : $this->right->clear($index - $this->right->getSize()); } /** * @param bool $withAir * @return array */ public function getContents($withAir = false){ $contents = []; for($i = 0; $i < $this->getSize(); ++$i){ $contents[$i] = $this->getItem($i); } return $contents; } /** * @param Item[] $items * @param bool $send */ public function setContents(array $items, $send = true){ if(count($items) > $this->size){ $items = array_slice($items, 0, $this->size, true); } for($i = 0; $i < $this->size; ++$i){ if(!isset($items[$i])){ if($i < $this->left->size){ if(isset($this->left->slots[$i])){ $this->clear($i); } }elseif(isset($this->right->slots[$i - $this->left->size])){ $this->clear($i); } }elseif(!$this->setItem($i, $items[$i])){ $this->clear($i); } } } /** * @param Player $who */ public function onOpen(Player $who){ parent::onOpen($who); if(count($this->getViewers()) === 1){ $pk = new BlockEventPacket(); $pk->x = $this->right->getHolder()->getX(); $pk->y = $this->right->getHolder()->getY(); $pk->z = $this->right->getHolder()->getZ(); $pk->case1 = 1; $pk->case2 = 2; if(($level = $this->right->getHolder()->getLevel()) instanceof Level){ $level->addChunkPacket($this->right->getHolder()->getX() >> 4, $this->right->getHolder()->getZ() >> 4, $pk); } } } /** * @param Player $who */ public function onClose(Player $who){ if(count($this->getViewers()) === 1){ $pk = new BlockEventPacket(); $pk->x = $this->right->getHolder()->getX(); $pk->y = $this->right->getHolder()->getY(); $pk->z = $this->right->getHolder()->getZ(); $pk->case1 = 1; $pk->case2 = 0; if(($level = $this->right->getHolder()->getLevel()) instanceof Level){ $level->addChunkPacket($this->right->getHolder()->getX() >> 4, $this->right->getHolder()->getZ() >> 4, $pk); } } parent::onClose($who); } /** * @return ChestInventory */ public function getLeftSide(){ return $this->left; } /** * @return ChestInventory */ public function getRightSide(){ return $this->right; } }targetItem = $droppedItem; } /** * @param Item $item */ public function setSourceItem(Item $item){ //Nothing to update } /** * @return null */ public function getInventory(){ return null; } /** * @return null */ public function getSlot(){ return null; } /** * @param Player $source */ public function sendSlotUpdate(Player $source){ //Nothing to update } /** * @return array */ public function getChange(){ return ["in" => $this->getTargetItem(), "out" => null]; } /** * @param Player $source * * @return bool */ public function execute(Player $source) : bool{ $droppedItem = $this->getTargetItem(); if(!$source->getServer()->allowInventoryCheats and !$source->isCreative()){ if(!$source->getFloatingInventory()->contains($droppedItem)){ return false; } $source->getFloatingInventory()->removeItem($droppedItem); } $source->dropItem($droppedItem); return true; } }holder; } }holder; } /** * @return int */ public function getResultSlotIndex(){ return -1; //enchanting tables don't have result slots, they modify the item in the target slot instead } /** * @param Player $who */ public function onOpen(Player $who){ parent::onOpen($who); if($this->levels == null){ $this->bookshelfAmount = $this->countBookshelf(); if($this->bookshelfAmount < 0){ $this->bookshelfAmount = 0; } if($this->bookshelfAmount > 15){ $this->bookshelfAmount = 15; } $base = mt_rand(1, 8) + ($this->bookshelfAmount / 2) + mt_rand(0, $this->bookshelfAmount); $this->levels = [ 0 => max($base / 3, 1), 1 => (($base * 2) / 3 + 1), 2 => max($base, $this->bookshelfAmount * 2) ]; } } /* public function onSlotChange($index, $before, $send){ parent::onSlotChange($index, $before, $send); if($index === 0){ $item = $this->getItem(0); if($item->getId() === Item::AIR){ $this->entries = null; }elseif($before->getId() == Item::AIR and !$item->hasEnchantments()){ //before enchant if($this->entries === null){ $enchantAbility = Enchantment::getEnchantAbility($item); $this->entries = []; for($i = 0; $i < 3; $i++){ $result = []; $level = $this->levels[$i]; $k = $level + mt_rand(0, round(round($enchantAbility / 4) * 2)) + 1; $bonus = ($this->randomFloat() + $this->randomFloat() - 1) * 0.15 + 1; $modifiedLevel = ($k * (1 + $bonus) + 0.5); $possible = EnchantmentLevelTable::getPossibleEnchantments($item, $modifiedLevel); $weights = []; $total = 0; for($j = 0; $j < count($possible); $j++){ $id = $possible[$j]->getId(); $weight = Enchantment::getEnchantWeight($id); $weights[$j] = $weight; $total += $weight; } $v = mt_rand(1, $total + 1); $sum = 0; for($key = 0; $key < count($weights); ++$key){ $sum += $weights[$key]; if($sum >= $v){ $key++; break; } } $key--; if(!isset($possible[$key])) return; $enchantment = $possible[$key]; $result[] = $enchantment; unset($possible[$key]); //Extra enchantment while(count($possible) > 0){ $modifiedLevel = round($modifiedLevel / 2); $v = mt_rand(0, 51); if($v <= ($modifiedLevel + 1)){ $possible = $this->removeConflictEnchantment($enchantment, $possible); $weights = []; $total = 0; for($j = 0; $j < count($possible); $j++){ $id = $possible[$j]->getId(); $weight = Enchantment::getEnchantWeight($id); $weights[$j] = $weight; $total += $weight; } $v = mt_rand(1, $total + 1); $sum = 0; for($key = 0; $key < count($weights); ++$key){ $sum += $weights[$key]; if($sum >= $v){ $key++; break; } } $key--; $enchantment = $possible[$key]; $result[] = $enchantment; unset($possible[$key]); }else{ break; } } $this->entries[$i] = new EnchantmentEntry($result, $level, Enchantment::getRandomName()); } $this->sendEnchantmentList(); } } } } */ /** * @param Player $who */ public function onClose(Player $who){ parent::onClose($who); $level = $this->getHolder()->getLevel(); for($i = 0; $i < 2; ++$i){ if($level instanceof Level) $level->dropItem($this->getHolder()->add(0.5, 0.5, 0.5), $this->getItem($i)); $this->clear($i); } if(count($this->getViewers()) === 0){ $this->levels = null; $this->entries = null; $this->bookshelfAmount = 0; } } /** * @param Enchantment[] $ent1 * @param Enchantment[] $ent2 * * @return bool */ public function checkEnts(array $ent1, array $ent2){ foreach($ent1 as $enchantment){ $hasResult = false; foreach($ent2 as $enchantment1){ if($enchantment->equals($enchantment1)){ $hasResult = true; continue; } } if(!$hasResult){ return false; } } return true; } /** * @param Player $who * @param Item $before * @param Item $after */ public function onEnchant(Player $who, Item $before, Item $after){ $result = ($before->getId() === Item::BOOK) ? new EnchantedBook() : $before; if(!$before->hasEnchantments() and $after->hasEnchantments() and $after->getId() == $result->getId() and $this->levels != null and $this->entries != null ){ $enchantments = $after->getEnchantments(); for($i = 0; $i < 3; $i++){ if($this->checkEnts($enchantments, $this->entries[$i]->getEnchantments())){ $lapis = $this->getItem(1); $level = $who->getXpLevel(); $cost = $this->entries[$i]->getCost(); if($lapis->getId() == Item::DYE and $lapis->getDamage() == Dye::BLUE and $lapis->getCount() > $i and $level >= $cost){ foreach($enchantments as $enchantment){ $result->addEnchantment($enchantment); } $this->setItem(0, $result); $lapis->setCount($lapis->getCount() - $i - 1); $this->setItem(1, $lapis); $who->takeXpLevel($i + 1); break; } } } } } /** * @return int */ public function countBookshelf() : int{ if($this->getHolder()->getLevel()->getServer()->countBookshelf){ $count = 0; $pos = $this->getHolder(); $offsets = [[2, 0], [-2, 0], [0, 2], [0, -2], [2, 1], [2, -1], [-2, 1], [-2, 1], [1, 2], [-1, 2], [1, -2], [-1, -2]]; for($i = 0; $i < 3; $i++){ foreach($offsets as $offset){ if($pos->getLevel()->getBlockIdAt($pos->x + $offset[0], $pos->y + $i, $pos->z + $offset[1]) == Block::BOOKSHELF){ $count++; } if($count >= 15){ break 2; } } } return $count; }else{ return mt_rand(0, 15); } } /** * @param Enchantment $enchantment * @param Enchantment[] $enchantments * * @return Enchantment[] */ public function removeConflictEnchantment(Enchantment $enchantment, array $enchantments){ if(count($enchantments) > 0){ foreach($enchantments as $e){ $id = $e->getId(); if($id == $enchantment->getId()){ unset($enchantments[$id]); continue; } if($id >= 0 and $id <= 4 and $enchantment->getId() >= 0 and $enchantment->getId() <= 4){ //Protection unset($enchantments[$id]); continue; } if($id >= 9 and $id <= 14 and $enchantment->getId() >= 9 and $enchantment->getId() <= 14){ //Weapon unset($enchantments[$id]); continue; } if(($id === Enchantment::TYPE_MINING_SILK_TOUCH and $enchantment->getId() === Enchantment::TYPE_MINING_FORTUNE) or ($id === Enchantment::TYPE_MINING_FORTUNE and $enchantment->getId() === Enchantment::TYPE_MINING_SILK_TOUCH)){ //Protection unset($enchantments[$id]); continue; } } } $result = []; if(count($enchantments) > 0){ foreach($enchantments as $enchantment){ $result[] = $enchantment; } } return $result; } } owner = $owner; parent::__construct(new FakeBlockMenu($this, $owner->getPosition()), InventoryType::get(InventoryType::ENDER_CHEST)); if($contents !== null){ if($contents instanceof ListTag){ //Saved data to be loaded into the inventory foreach($contents as $item){ $this->setItem($item["Slot"], Item::nbtDeserialize($item)); } }else{ throw new \InvalidArgumentException("Expecting ListTag, received " . gettype($contents)); } } } /** * @return Human|Player */ public function getOwner(){ return $this->owner; } /** * Set the fake block menu's position to a valid tile position * and send the inventory window to the owner * * @param Position $pos */ public function openAt(Position $pos){ $this->getHolder()->setComponents($pos->x, $pos->y, $pos->z); $this->getHolder()->setLevel($pos->getLevel()); $this->owner->addWindow($this); } /** * @return FakeBlockMenu */ public function getHolder(){ return $this->holder; } /** * @param Player $who */ public function onOpen(Player $who){ parent::onOpen($who); if(count($this->getViewers()) === 1){ $pk = new BlockEventPacket(); $pk->x = $this->getHolder()->getX(); $pk->y = $this->getHolder()->getY(); $pk->z = $this->getHolder()->getZ(); $pk->case1 = 1; $pk->case2 = 2; if(($level = $this->getHolder()->getLevel()) instanceof Level){ $level->addChunkPacket($this->getHolder()->getX() >> 4, $this->getHolder()->getZ() >> 4, $pk); } } } /** * @param Player $who */ public function onClose(Player $who){ if(count($this->getViewers()) === 1){ $pk = new BlockEventPacket(); $pk->x = $this->getHolder()->getX(); $pk->y = $this->getHolder()->getY(); $pk->z = $this->getHolder()->getZ(); $pk->case1 = 1; $pk->case2 = 0; if(($level = $this->getHolder()->getLevel()) instanceof Level){ $level->addChunkPacket($this->getHolder()->getX() >> 4, $this->getHolder()->getZ() >> 4, $pk); } } parent::onClose($who); } }inventory = $inventory; parent::__construct($pos->x, $pos->y, $pos->z, $pos->level); } /** * @return Inventory */ public function getInventory(){ return $this->inventory; } } 1600, Item::COAL_BLOCK => 16000, Item::TRUNK => 300, //Item::BROWN_MUSHROOM_BLOCK => 300, //Item::RED_MUSHROOM_BLOCK => 300, Item::WOODEN_PLANKS => 300, Item::SAPLING => 100, Item::WOODEN_AXE => 200, Item::WOODEN_PICKAXE => 200, Item::WOODEN_SWORD => 200, Item::WOODEN_SHOVEL => 200, Item::WOODEN_HOE => 200, Item::WOODEN_PRESSURE_PLATE => 300, Item::STICK => 100, Item::FENCE => 300, Item::FENCE_GATE => 300, Item::FENCE_GATE_SPRUCE => 300, Item::FENCE_GATE_BIRCH => 300, Item::FENCE_GATE_JUNGLE => 300, Item::FENCE_GATE_ACACIA => 300, Item::FENCE_GATE_DARK_OAK => 300, Item::WOODEN_STAIRS => 300, Item::SPRUCE_WOOD_STAIRS => 300, Item::BIRCH_WOOD_STAIRS => 300, Item::JUNGLE_WOOD_STAIRS => 300, Item::TRAPDOOR => 300, Item::WORKBENCH => 300, Item::NOTEBLOCK => 300, Item::BOOKSHELF => 300, Item::CHEST => 300, Item::TRAPPED_CHEST => 300, Item::DAYLIGHT_SENSOR => 300, Item::BUCKET => 20000, Item::BLAZE_ROD => 2400, ]; }holder; } /** * @return Item */ public function getResult(){ return $this->getItem(self::RESULT); } /** * @return Item */ public function getFuel(){ return $this->getItem(self::FUEL); } /** * @return Item */ public function getSmelting(){ return $this->getItem(self::SMELTING); } /** * @param Item $item * * @return bool */ public function setResult(Item $item){ return $this->setItem(self::RESULT, $item); } /** * @param Item $item * * @return bool */ public function setFuel(Item $item){ return $this->setItem(self::FUEL, $item); } /** * @param Item $item * * @return bool */ public function setSmelting(Item $item){ return $this->setItem(self::SMELTING, $item); } /** * @param int $index * @param Item $before * @param bool $send */ public function onSlotChange($index, $before, $send){ parent::onSlotChange($index, $before, $send); $this->getHolder()->scheduleUpdate(); } } output = clone $result; $this->ingredient = clone $ingredient; } /** * @return null */ public function getId(){ return $this->id; } /** * @param UUID $id */ public function setId(UUID $id){ if($this->id !== null){ throw new \InvalidStateException("Id is already set"); } $this->id = $id; } /** * @param Item $item */ public function setInput(Item $item){ $this->ingredient = clone $item; } /** * @return Item */ public function getInput(){ return clone $this->ingredient; } /** * @return Item */ public function getResult(){ return clone $this->output; } public function registerToCraftingManager(){ Server::getInstance()->getCraftingManager()->registerFurnaceRecipe($this); } }holder; } } 0){ return; } //TODO: move network stuff out of here //TODO: move inventory data to json static::$default = [ static::CHEST => new InventoryType(27, "Chest", InventoryNetworkIds::CONTAINER), static::DOUBLE_CHEST => new InventoryType(27 + 27, "Double Chest", InventoryNetworkIds::CONTAINER), static::PLAYER => new InventoryType(36 + 4, "Player", InventoryNetworkIds::INVENTORY), //36 CONTAINER, 4 ARMOR static::CRAFTING => new InventoryType(5, "Crafting", InventoryNetworkIds::INVENTORY), //yes, the use of INVENTORY is intended! 4 CRAFTING slots, 1 RESULT static::WORKBENCH => new InventoryType(10, "Crafting", InventoryNetworkIds::WORKBENCH), //9 CRAFTING slots, 1 RESULT static::FURNACE => new InventoryType(3, "Furnace", InventoryNetworkIds::FURNACE), //2 INPUT, 1 OUTPUT static::ENCHANT_TABLE => new InventoryType(2, "Enchant", InventoryNetworkIds::ENCHANTMENT), //1 INPUT/OUTPUT, 1 LAPIS static::BREWING_STAND => new InventoryType(4, "Brewing", InventoryNetworkIds::BREWING_STAND), //1 INPUT, 3 POTION static::ANVIL => new InventoryType(3, "Anvil", InventoryNetworkIds::ANVIL), //2 INPUT, 1 OUTPUT static::DISPENSER => new InventoryType(9, "Dispenser", InventoryNetworkIds::DISPENSER), //9 CONTAINER static::DROPPER => new InventoryType(9, "Dropper", InventoryNetworkIds::DROPPER), //9 CONTAINER static::HOPPER => new InventoryType(5, "Hopper", InventoryNetworkIds::HOPPER), //5 CONTAINER static::ENDER_CHEST => new InventoryType(27, "Ender Chest", InventoryNetworkIds::CONTAINER), static::BEACON => new InventoryType(0, "Beacon", InventoryNetworkIds::BEACON), //信标 static::PLAYER_FLOATING => new InventoryType(36, "Floating", null) //Mirror all slots of main inventory (needed for large item pickups) ]; } /** * @param int $defaultSize * @param string $defaultTitle * @param int $typeId */ private function __construct($defaultSize, $defaultTitle, $typeId = 0){ $this->size = $defaultSize; $this->title = $defaultTitle; $this->typeId = $typeId; } /** * @return int */ public function getDefaultSize(){ return $this->size; } /** * @return string */ public function getDefaultTitle(){ return $this->title; } /** * @return int */ public function getNetworkType(){ return $this->typeId; } }uuid = $uuid; } }hotbar = range(0, $this->getHotbarSize() - 1, 1); parent::__construct($player, InventoryType::get(InventoryType::PLAYER)); if($contents !== null){ if($contents instanceof ListTag){ //Saved data to be loaded into the inventory foreach($contents as $item){ if($item["Slot"] >= 0 and $item["Slot"] < $this->getHotbarSize()){ //Hotbar if(isset($item["TrueSlot"])){ //Valid slot was found, change the linkage to this slot if(0 <= $item["TrueSlot"] and $item["TrueSlot"] < $this->getSize()){ $this->hotbar[$item["Slot"]] = $item["TrueSlot"]; }elseif($item["TrueSlot"] < 0){ //Link to an empty slot (empty hand) $this->hotbar[$item["Slot"]] = -1; } } /* If TrueSlot is not set, leave the slot index as its default which was filled in above * This only overwrites slot indexes for valid links */ }elseif($item["Slot"] >= 100 and $item["Slot"] < 104){ //Armor $this->setItem($this->getSize() + $item["Slot"] - 100, Item::nbtDeserialize($item), false); }else{ $this->setItem($item["Slot"] - $this->getHotbarSize(), Item::nbtDeserialize($item), false); } } }else{ throw new \InvalidArgumentException("Expecting ListTag, received " . gettype($contents)); } } } /** * @return int */ public function getSize(){ return parent::getSize() - 4; //Remove armor slots } /** * @param $size */ public function setSize($size){ parent::setSize($size + 4); $this->sendContents($this->getViewers()); } /** * @param int $index * * @return int * * Returns the index of the inventory slot linked to the specified hotbar slot */ public function getHotbarSlotIndex($index){ return ($index >= 0 and $index < $this->getHotbarSize()) ? $this->hotbar[$index] : -1; } /** * @deprecated * * Changes the linkage of the specified hotbar slot. This should never be done unless it is requested by the client. * * @param int $index * @param int $slot */ public function setHotbarSlotIndex($index, $slot){ if($this->getHolder()->getServer()->getProperty("settings.deprecated-verbose") !== false){ trigger_error("Do not attempt to change hotbar links in plugins!", E_USER_DEPRECATED); } } /** * @return int * * Returns the index of the inventory slot the player is currently holding */ public function getHeldItemIndex(){ return $this->itemInHandIndex; } /** * @param int $hotbarSlotIndex * @param bool $sendToHolder * @param int $slotMapping * * Sets which hotbar slot the player is currently holding. * Allows slot remapping as specified by a MobEquipmentPacket. DO NOT CHANGE SLOT MAPPING IN PLUGINS! * This new implementation is fully compatible with older APIs. * NOTE: Slot mapping is the raw slot index sent by MCPE, which will be between 9 and 44. */ public function setHeldItemIndex($hotbarSlotIndex, $sendToHolder = true, $slotMapping = null){ if($slotMapping !== null){ //Get the index of the slot in the actual inventory $slotMapping -= $this->getHotbarSize(); } if(0 <= $hotbarSlotIndex and $hotbarSlotIndex < $this->getHotbarSize()){ $this->itemInHandIndex = $hotbarSlotIndex; if($slotMapping !== null){ /* Handle a hotbar slot mapping change. This allows PE to select different inventory slots. * This is the only time slot mapping should ever be changed. */ if($slotMapping < 0 or $slotMapping >= $this->getSize()){ //Mapping was not in range of the inventory, set it to -1 //This happens if the client selected a blank slot (sends 255) $slotMapping = -1; } $item = $this->getItem($slotMapping); if($this->getHolder() instanceof Player){ Server::getInstance()->getPluginManager()->callEvent($ev = new PlayerItemHeldEvent($this->getHolder(), $item, $slotMapping, $hotbarSlotIndex)); if($ev->isCancelled()){ $this->sendHeldItem($this->getHolder()); $this->sendContents($this->getHolder()); return; } } if(($key = array_search($slotMapping, $this->hotbar)) !== false and $slotMapping !== -1){ /* Do not do slot swaps if the slot was null * Chosen slot is already linked to a hotbar slot, swap the two slots around. * This will already have been done on the client-side so no changes need to be sent. */ $this->hotbar[$key] = $this->hotbar[$this->itemInHandIndex]; } $this->hotbar[$this->itemInHandIndex] = $slotMapping; } $this->sendHeldItem($this->getHolder()->getViewers()); if($sendToHolder){ $this->sendHeldItem($this->getHolder()); } } } /** * @return Item * * Returns the item the player is currently holding */ public function getItemInHand(){ $item = $this->getItem($this->getHeldItemSlot()); if($item instanceof Item){ return $item; }else{ return Item::get(Item::AIR, 0, 0); } } /** * @param Item $item * * @return bool * * Sets the item in the inventory slot the player is currently holding. */ public function setItemInHand(Item $item){ return $this->setItem($this->getHeldItemSlot(), $item); } /** * @return int[] * * Returns an array of hotbar indices */ public function getHotbar(){ return $this->hotbar; } /** * @return int * * Returns the inventory slot index of the currently equipped slot */ public function getHeldItemSlot(){ return $this->getHotbarSlotIndex($this->itemInHandIndex); } /** * @deprecated * * @param int $slot */ public function setHeldItemSlot($slot){ } /** * @param Player|Player[] $target */ public function sendHeldItem($target){ $item = $this->getItemInHand(); $pk = new MobEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->item = $item; $pk->slot = $this->getHeldItemSlot(); $pk->selectedSlot = $this->getHeldItemIndex(); $pk->windowId = ContainerSetContentPacket::SPECIAL_INVENTORY; if(!is_array($target)){ $target->dataPacket($pk); if($this->getHeldItemSlot() !== -1 and $target === $this->getHolder()){ $this->sendSlot($this->getHeldItemSlot(), $target); } }else{ $this->getHolder()->getLevel()->getServer()->broadcastPacket($target, $pk); if($this->getHeldItemSlot() !== -1 and in_array($this->getHolder(), $target)){ $this->sendSlot($this->getHeldItemSlot(), $this->getHolder()); } } } /** * @param int $index * @param Item $before * @param bool $send */ public function onSlotChange($index, $before, $send){ if($send){ $holder = $this->getHolder(); if(!$holder instanceof Player or !$holder->spawned){ return; } parent::onSlotChange($index, $before, $send); } if($index === $this->itemInHandIndex){ $this->sendHeldItem($this->getHolder()->getViewers()); if($send){ $this->sendHeldItem($this->getHolder()); } }elseif($index >= $this->getSize()){ //Armour equipment $this->sendArmorSlot($index, $this->getViewers()); $this->sendArmorSlot($index, $this->getHolder()->getViewers()); } } /** * @return int */ public function getHotbarSize(){ return 9; } /** * @param $index * * @return Item */ public function getArmorItem($index){ return $this->getItem($this->getSize() + $index); } /** * @param $index * @param Item $item * * @return bool */ public function setArmorItem($index, Item $item){ return $this->setItem($this->getSize() + $index, $item); } /** * @param $index * @param $cost */ public function damageArmor($index, $cost){ $this->slots[$this->getSize() + $index]->useOn($this->slots[$this->getSize() + $index], $cost); if($this->slots[$this->getSize() + $index]->getDamage() >= $this->slots[$this->getSize() + $index]->getMaxDurability()){ $this->setItem($this->getSize() + $index, Item::get(Item::AIR, 0, 0)); } $this->sendArmorContents($this->getViewers()); } /** * @return Item */ public function getHelmet(){ return $this->getItem($this->getSize()); } /** * @return Item */ public function getChestplate(){ return $this->getItem($this->getSize() + 1); } /** * @return Item */ public function getLeggings(){ return $this->getItem($this->getSize() + 2); } /** * @return Item */ public function getBoots(){ return $this->getItem($this->getSize() + 3); } /** * @param Item $helmet * * @return bool */ public function setHelmet(Item $helmet){ return $this->setItem($this->getSize(), $helmet); } /** * @param Item $chestplate * * @return bool */ public function setChestplate(Item $chestplate){ return $this->setItem($this->getSize() + 1, $chestplate); } /** * @param Item $leggings * * @return bool */ public function setLeggings(Item $leggings){ return $this->setItem($this->getSize() + 2, $leggings); } /** * @param Item $boots * * @return bool */ public function setBoots(Item $boots){ return $this->setItem($this->getSize() + 3, $boots); } /** * @param int $index * @param Item $item * @param bool $send * * @return bool */ public function setItem($index, Item $item, $send = true){ if($index < 0 or $index >= $this->size){ return false; }elseif($item->getId() === 0 or $item->getCount() <= 0){ return $this->clear($index, $send); } if($index >= $this->getSize()){ //Armor change Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $this->getItem($index), $item, $index)); if($ev->isCancelled() and $this->getHolder() instanceof Human){ $this->sendArmorSlot($index, $this->getViewers()); return false; } $item = $ev->getNewItem(); }else{ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $this->getItem($index), $item, $index)); if($ev->isCancelled()){ $this->sendSlot($index, $this->getViewers()); return false; } $item = $ev->getNewItem(); } $old = $this->getItem($index); $this->slots[$index] = clone $item; $this->onSlotChange($index, $old, $send); return true; } /** * @param int $index * @param bool $send * * @return bool */ public function clear($index, $send = true){ if(isset($this->slots[$index])){ $item = Item::get(Item::AIR, 0, 0); $old = $this->slots[$index]; if($index >= $this->getSize() and $index < $this->size){ //Armor change Server::getInstance()->getPluginManager()->callEvent($ev = new EntityArmorChangeEvent($this->getHolder(), $old, $item, $index)); if($ev->isCancelled()){ if($index >= $this->size){ $this->sendArmorSlot($index, $this->getViewers()); }else{ $this->sendSlot($index, $this->getViewers()); } return false; } $item = $ev->getNewItem(); }else{ Server::getInstance()->getPluginManager()->callEvent($ev = new EntityInventoryChangeEvent($this->getHolder(), $old, $item, $index)); if($ev->isCancelled()){ if($index >= $this->size){ $this->sendArmorSlot($index, $this->getViewers()); }else{ $this->sendSlot($index, $this->getViewers()); } return false; } $item = $ev->getNewItem(); } if($item->getId() !== Item::AIR){ $this->slots[$index] = clone $item; }else{ unset($this->slots[$index]); } $this->onSlotChange($index, $old, $send); } return true; } /** * @return Item[] */ public function getArmorContents(){ $armor = []; for($i = 0; $i < 4; ++$i){ $armor[$i] = $this->getItem($this->getSize() + $i); } return $armor; } public function clearAll($send = true){ $limit = $this->getSize() + 4; for($index = 0; $index < $limit; ++$index){ $this->clear($index, false); } $this->hotbar = range(0, $this->getHotbarSize() - 1, 1); $this->sendContents($this->getViewers()); } /** * @param Player|Player[] $target */ public function sendArmorContents($target){ if($target instanceof Player){ $target = [$target]; } $armor = $this->getArmorContents(); $pk = new MobArmorEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->slots = $armor; $pk->encode(); $pk->isEncoded = true; foreach($target as $player){ if($player === $this->getHolder()){ $pk2 = new ContainerSetContentPacket(); $pk2->windowid = ContainerSetContentPacket::SPECIAL_ARMOR; $pk2->slots = $armor; $pk2->targetEid = $player->getId(); $player->dataPacket($pk2); }else{ $player->dataPacket($pk); } } } /** * @param Item[] $items */ public function setArmorContents(array $items){ for($i = 0; $i < 4; ++$i){ if(!isset($items[$i]) or !($items[$i] instanceof Item)){ $items[$i] = Item::get(Item::AIR, 0, 0); } if($items[$i]->getId() === Item::AIR){ $this->clear($this->getSize() + $i); }else{ $this->setItem($this->getSize() + $i, $items[$i]); } } } /** * @param int $index * @param Player|Player[] $target */ public function sendArmorSlot($index, $target){ if($target instanceof Player){ $target = [$target]; } $armor = $this->getArmorContents(); $pk = new MobArmorEquipmentPacket(); $pk->eid = $this->getHolder()->getId(); $pk->slots = $armor; $pk->encode(); $pk->isEncoded = true; foreach($target as $player){ if($player === $this->getHolder()){ /** @var Player $player */ $pk2 = new ContainerSetSlotPacket(); $pk2->windowid = ContainerSetContentPacket::SPECIAL_ARMOR; $pk2->slot = $index - $this->getSize(); $pk2->item = $this->getItem($index); $player->dataPacket($pk2); }else{ $player->dataPacket($pk); } } } /** * @param Player|Player[] $target */ public function sendContents($target){ if($target instanceof Player){ $target = [$target]; } $pk = new ContainerSetContentPacket(); $pk->slots = []; for($i = 0; $i < $this->getSize(); ++$i){ //Do not send armor by error here $pk->slots[$i] = $this->getItem($i); } //Because PE is stupid and shows 9 less slots than you send it, give it 9 dummy slots so it shows all the REAL slots. for($i = $this->getSize(); $i < $this->getSize() + $this->getHotbarSize(); ++$i){ $pk->slots[$i] = Item::get(Item::AIR, 0, 0); } foreach($target as $player){ $pk->hotbar = []; if($player === $this->getHolder()){ for($i = 0; $i < $this->getHotbarSize(); ++$i){ $index = $this->getHotbarSlotIndex($i); $pk->hotbar[$i] = $index <= -1 ? -1 : $index + $this->getHotbarSize(); } } if(($id = $player->getWindowId($this)) === -1 or $player->spawned !== true){ $this->close($player); continue; } $pk->windowid = $id; $pk->targetEid = $player->getId(); $player->dataPacket(clone $pk); } } /** * @param int $index * @param Player|Player[] $target */ public function sendSlot($index, $target){ if($target instanceof Player){ $target = [$target]; } $pk = new ContainerSetSlotPacket(); $pk->slot = $index; $pk->item = clone $this->getItem($index); foreach($target as $player){ if($player === $this->getHolder()){ /** @var Player $player */ $pk->windowid = 0; $player->dataPacket(clone $pk); }else{ if(($id = $player->getWindowId($this)) === -1){ $this->close($player); continue; } $pk->windowid = $id; $player->dataPacket(clone $pk); } } } /** * @return Human|Player */ public function getHolder(){ return parent::getHolder(); } } 3){ throw new \InvalidStateException("Crafting rows should be 1, 2, 3 wide, not $width"); } $this->ingredients[] = array_fill(0, $width, null); } $this->output = clone $result; } /** * @return int */ public function getWidth(){ return count($this->ingredients[0]); } /** * @return int */ public function getHeight(){ return count($this->ingredients); } /** * @return Item */ public function getResult(){ return $this->output; } /** * @return null */ public function getId(){ return $this->id; } /** * @param UUID $id */ public function setId(UUID $id){ if($this->id !== null){ throw new \InvalidStateException("Id is already set"); } $this->id = $id; } /** * @param $x * @param $y * @param Item $item * * @return $this */ public function addIngredient($x, $y, Item $item){ $this->ingredients[$y][$x] = clone $item; return $this; } /** * @param string $key * @param Item $item * * @return $this * @throws \Exception */ public function setIngredient($key, Item $item){ if(!array_key_exists($key, $this->shape)){ throw new \Exception("Symbol does not appear in the shape: " . $key); } $item->setCount(1); $this->fixRecipe($key, $item); return $this; } /** * @param $key * @param $item */ protected function fixRecipe($key, $item){ foreach($this->shapeItems[$key] as $entry){ $this->ingredients[$entry->y][$entry->x] = clone $item; } } /** * @return Item[][] */ public function getIngredientMap(){ $ingredients = []; foreach($this->ingredients as $y => $row){ $ingredients[$y] = []; foreach($row as $x => $ingredient){ if($ingredient !== null){ $ingredients[$y][$x] = clone $ingredient; }else{ $ingredients[$y][$x] = Item::get(Item::AIR); } } } return $ingredients; } /** * @return Item[] */ public function getIngredientList(){ $ingredients = []; for($x = 0; $x < 3; ++$x){ for($y = 0; $y < 3; ++$y){ if(!empty($this->ingredients[$x][$y])){ if($this->ingredients[$x][$y]->getId() !== Item::AIR){ $ingredients[] = clone $this->ingredients[$x][$y]; } } } } return $ingredients; } /** * @param $x * @param $y * * @return null|Item */ public function getIngredient($x, $y){ return isset($this->ingredients[$y][$x]) ? $this->ingredients[$y][$x] : Item::get(Item::AIR); } /** * @return string[] */ public function getShape(){ return $this->shape; } public function registerToCraftingManager(){ Server::getInstance()->getCraftingManager()->registerShapedRecipe($this); } } 3){ throw new \InvalidStateException("Crafting rows should be 1, 2, 3 wide, not $width"); } $this->ingredients[] = array_fill(0, $width, null); } $this->output = clone $result; } /** * @return int */ public function getWidth(){ return count($this->ingredients[0]); } /** * @return int */ public function getHeight(){ return count($this->ingredients); } /** * @return Item */ public function getResult(){ return $this->output; } /** * @return null */ public function getId(){ return $this->id; } /** * @param UUID $id */ public function setId(UUID $id){ if($this->id !== null){ throw new \InvalidStateException("Id is already set"); } $this->id = $id; } /** * @param $x * @param $y * @param Item $item * * @return $this */ public function addIngredient($x, $y, Item $item){ $this->ingredients[$y][$x] = clone $item; return $this; } /** * @param string $key * @param Item $item * * @return $this * @throws \Exception */ public function setIngredient($key, Item $item){ if(!array_key_exists($key, $this->shape)){ throw new \Exception("Symbol does not appear in the shape: " . $key); } $this->fixRecipe($key, $item); return $this; } /** * @param $key * @param $item */ protected function fixRecipe($key, $item){ foreach($this->shapeItems[$key] as $entry){ $this->ingredients[$entry->y][$entry->x] = clone $item; } } /** * @return Item[][] */ public function getIngredientMap(){ $ingredients = []; foreach($this->ingredients as $y => $row){ $ingredients[$y] = []; foreach($row as $x => $ingredient){ if($ingredient !== null){ $ingredients[$y][$x] = clone $ingredient; }else{ $ingredients[$y][$x] = Item::get(Item::AIR); } } } return $ingredients; } /** * @param $x * @param $y * * @return null|Item */ public function getIngredient($x, $y){ return isset($this->ingredients[$y][$x]) ? $this->ingredients[$y][$x] : Item::get(Item::AIR); } /** * @return string[] */ public function getShape(){ return $this->shape; } public function registerToCraftingManager(){ Server::getInstance()->getCraftingManager()->registerShapedRecipe($this); } }output = clone $result; } /** * @return null */ public function getId(){ return $this->id; } /** * @param UUID $id */ public function setId(UUID $id){ if($this->id !== null){ throw new \InvalidStateException("Id is already set"); } $this->id = $id; } /** * @return Item */ public function getResult(){ return clone $this->output; } /** * @param Item $item * * @returns ShapelessRecipe * * @throws \InvalidArgumentException */ public function addIngredient(Item $item){ if(count($this->ingredients) >= 9){ throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients"); } $it = clone $item; $it->setCount(1); while($item->getCount() > 0){ $this->ingredients[] = clone $it; $item->setCount($item->getCount() - 1); } return $this; } /** * @param Item $item * * @return $this */ public function removeIngredient(Item $item){ foreach($this->ingredients as $index => $ingredient){ if($item->getCount() <= 0){ break; } if($ingredient->equals($item, !$item->hasAnyDamageValue(), $item->hasCompoundTag())){ unset($this->ingredients[$index]); $item->setCount($item->getCount() - 1); } } return $this; } /** * @return Item[] */ public function getIngredientList(){ $ingredients = []; foreach($this->ingredients as $ingredient){ $ingredients[] = clone $ingredient; } return $ingredients; } /** * @return int */ public function getIngredientCount(){ $count = 0; foreach($this->ingredients as $ingredient){ $count += $ingredient->getCount(); } return $count; } public function registerToCraftingManager(){ Server::getInstance()->getCraftingManager()->registerShapelessRecipe($this); } }player = $player; $this->transactionQueue = new \SplQueue(); $this->transactionsToRetry = new \SplQueue(); } /** * @return Player */ public function getPlayer(){ return $this->player; } /** * @return Inventory[] */ public function getInventories(){ return $this->inventories; } /** * @return \SplQueue */ public function getTransactions(){ return $this->transactionQueue; } /** * @return int */ public function getTransactionCount(){ return $this->transactionCount; } /** * @param Transaction $transaction */ public function addTransaction(Transaction $transaction){ $this->transactionQueue->enqueue($transaction); if($transaction->getInventory() instanceof Inventory){ /** For dropping items, the target inventory is open air, a.k.a. null. */ $this->inventories[spl_object_hash($transaction)] = $transaction->getInventory(); } $this->lastUpdate = microtime(true); $this->transactionCount += 1; } public function execute(){ /** @var Transaction[] */ $failed = []; while(!$this->transactionsToRetry->isEmpty()){ //Some failed transactions are waiting from the previous execution to be retried $this->transactionQueue->enqueue($this->transactionsToRetry->dequeue()); } if(!$this->transactionQueue->isEmpty()){ $this->player->getServer()->getPluginManager()->callEvent($ev = new InventoryTransactionEvent($this)); }else{ return; } while(!$this->transactionQueue->isEmpty()){ $transaction = $this->transactionQueue->dequeue(); if($ev->isCancelled()){ $this->transactionCount -= 1; $transaction->sendSlotUpdate($this->player); //Send update back to client for cancelled transaction unset($this->inventories[spl_object_hash($transaction)]); continue; }elseif(!$transaction->execute($this->player)){ $transaction->addFailure(); if($transaction->getFailures() >= self::DEFAULT_ALLOWED_RETRIES){ /* Transaction failed completely after several retries, hold onto it to send a slot update */ $this->transactionCount -= 1; $failed[] = $transaction; }else{ /* Add the transaction to the back of the queue to be retried on the next tick */ $this->transactionsToRetry->enqueue($transaction); } continue; } $this->transactionCount -= 1; $transaction->setSuccess(); $transaction->sendSlotUpdate($this->player); unset($this->inventories[spl_object_hash($transaction)]); } foreach($failed as $f){ $f->sendSlotUpdate($this->player); unset($this->inventories[spl_object_hash($f)]); } } }getContents() as $slot => $item){ if($slot === $this->getResultSlotIndex()){ //Do not drop the item in the result slot - it is a virtual item and does not actually exist. continue; } $who->dropItem($item); } $this->clearAll(); } }block = Block::get(Item::ACACIA_DOOR_BLOCK); parent::__construct(self::ACACIA_DOOR, 0, $count, "Acacia Door"); } }isUnbreakable()){ return true; } $unbreakings = [ 0 => 100, 1 => 80, 2 => 73, 3 => 70 ]; $unbreakingl = $this->getEnchantmentLevel(Enchantment::TYPE_MINING_DURABILITY); if(mt_rand(1, 100) > $unbreakings[$unbreakingl]){ return true; } $this->setDamage($this->getDamage() + $cost); if($this->getDamage() >= $this->getMaxDurability()){ $this->setCount(0); } return true; } /** * @return bool */ public function isUnbreakable(){ $tag = $this->getNamedTagEntry("Unbreakable"); return $tag !== null and $tag->getValue() > 0; } /** * @param Color $color */ public function setCustomColor(Color $color){ if(($hasTag = $this->hasCompoundTag())){ $tag = $this->getNamedTag(); }else{ $tag = new CompoundTag("", []); } $tag->customColor = new IntTag("customColor", $color->getColorCode()); $this->setCompoundTag($tag); } /** * @return mixed|null */ public function getCustomColor(){ if(!$this->hasCompoundTag()) return null; $tag = $this->getNamedTag(); if(isset($tag->customColor)){ return $tag["customColor"]; } return null; } public function clearCustomColor(){ if(!$this->hasCompoundTag()) return; $tag = $this->getNamedTag(); if(isset($tag->customColor)){ unset($tag->customColor); } $this->setCompoundTag($tag); } /** * @return bool */ public function getArmorTier(){ return false; } /** * @return bool */ public function getArmorType(){ return false; } /** * @return bool */ public function getMaxDurability(){ return false; } /** * @return bool */ public function getArmorValue(){ return false; } /** * @return bool */ public function isHelmet(){ return false; } /** * @return bool */ public function isChestplate(){ return false; } /** * @return bool */ public function isLeggings(){ return false; } /** * @return bool */ public function isBoots(){ return false; } }block = Block::get(Item::BED_BLOCK, $meta); parent::__construct(self::BED, $meta, $count, "Bed"); //TODO: Bed Names } /** * @return int */ public function getMaxStackSize() : int{ return 1; } } block = Block::get(Item::BEETROOT_BLOCK); parent::__construct(self::BEETROOT_SEEDS, 0, $count, "Beetroot Seeds"); } }block = Block::get(Item::BIRCH_DOOR_BLOCK); parent::__construct(self::BIRCH_DOOR, 0, $count, "Birch Door"); } }getSide($face); $boat = new BoatEntity($player->getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $realPos->getX() + 0.5), new DoubleTag("", $realPos->getY()), new DoubleTag("", $realPos->getZ() + 0.5) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), "WoodID" => new IntTag("WoodID", $this->getDamage()) ])); $boat->spawnToAll(); if($player->isSurvival()){ $item = $player->getInventory()->getItemInHand(); $count = $item->getCount(); if(--$count <= 0){ $player->getInventory()->setItemInHand(Item::get(Item::AIR)); return true; } $item->setCount($count); $player->getInventory()->setItemInHand($item); } return true; } } block = Block::get(Block::BREWING_STAND_BLOCK); parent::__construct(self::BREWING_STAND, $meta, $count, "Brewing Stand"); } } meta); if($targetBlock instanceof Air){ if($target instanceof Liquid and $target->getDamage() === 0){ $result = clone $this; $id = $target->getId(); if($id == self::STILL_WATER){ $id = self::WATER; } if($id == self::STILL_LAVA){ $id = self::LAVA; } $result->setDamage($id); $player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketFillEvent($player, $block, $face, $this, $result)); if(!$ev->isCancelled()){ $player->getLevel()->setBlock($target, new Air(), true, true); if($player->isSurvival()){ $player->getInventory()->setItemInHand($ev->getItem()); } return true; }else{ $player->getInventory()->sendContents($player); } } }elseif($targetBlock instanceof Liquid){ $result = clone $this; $result->setDamage(0); $player->getServer()->getPluginManager()->callEvent($ev = new PlayerBucketEmptyEvent($player, $block, $face, $this, $result)); if(!$ev->isCancelled()){ //Only disallow water placement in the Nether, allow other liquids to be placed //In vanilla, water buckets are emptied when used in the Nether, but no water placed. if(!($player->getLevel()->getDimension() === Level::DIMENSION_NETHER and $targetBlock->getId() === self::WATER)){ $player->getLevel()->setBlock($block, $targetBlock, true, true); } if($player->isSurvival()){ $player->getInventory()->setItemInHand($ev->getItem()); } return true; }else{ $player->getInventory()->sendContents($player); } } return false; } }block = Block::get(Item::CAKE_BLOCK); parent::__construct(self::CAKE, 0, $count, "Cake"); } /** * @return int */ public function getMaxStackSize() : int{ return 1; } }block = Block::get(Item::CARROT_BLOCK); parent::__construct(self::CARROT, 0, $count, "Carrot"); } /** * @return int */ public function getFoodRestore() : int{ return 3; } /** * @return float */ public function getSaturationRestore() : float{ return 4.8; } } block = Block::get(Block::CAULDRON_BLOCK); parent::__construct(self::CAULDRON, $meta, $count, "Cauldron"); } /** * @return int */ public function getMaxStackSize() : int{ return 1; } }meta === 1){ $this->name = "Charcoal"; } } }meta === self::FISH_SALMON ? 6 : 5; } /** * @return float */ public function getSaturationRestore() : float{ return $this->meta === self::FISH_SALMON ? 9.6 : 6; } } block = Block::get(Item::DARK_OAK_DOOR_BLOCK); parent::__construct(self::DARK_OAK_DOOR, 0, $count, "Dark Oak Door"); } }block = Block::get(Item::COCOA_BLOCK); parent::__construct(self::DYE, 3, $count, "Cocoa Beans"); }else{ parent::__construct(self::DYE, $meta, $count, $this->getNameByMeta($meta)); } } /** * @param int $meta * * @return string */ public function getNameByMeta(int $meta) : string{ switch($meta){ case self::BLACK: return "Ink Sac"; case self::RED: return "Rose Red"; case self::GREEN: return "Cactus Green"; case self::BROWN: return "Cocoa Beans"; case self::BLUE: return "Lapis Lazuli"; case self::PURPLE: return "Purple Dye"; case self::CYAN: return "Cyan Dye"; case self::SILVER: return "Light Gray Dye"; case self::GRAY: return "Gray Dye"; case self::PINK: return "Pink Dye"; case self::LIME: return "Lime Dye"; case self::YELLOW: return "Dandelion Yellow"; case self::LIGHT_BLUE: return "Light Blue Dye"; case self::MAGENTA: return "Magenta Dye"; case self::ORANGE: return "Orange Dye"; case self::WHITE: return "Bone Meal"; default: return "Dye"; } } } canBeConsumed(); } /** * @return int */ public function getFoodRestore() : int{ return 4; } /** * @return float */ public function getSaturationRestore() : float{ return 9.6; } /** * @return array */ public function getAdditionalEffects() : array{ return [ Effect::getEffect(Effect::REGENERATION)->setDuration(600)->setAmplifier(4), Effect::getEffect(Effect::DAMAGE_RESISTANCE)->setDuration(6000)->setAmplifier(0), Effect::getEffect(Effect::ABSORPTION)->setDuration(2400)->setAmplifier(3), Effect::getEffect(Effect::FIRE_RESISTANCE)->setDuration(6000)->setAmplifier(0), ]; } } temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @param Level $level * @param Player $player * @param Block $block * @param Block $target * @param $face * @param $fx * @param $fy * @param $fz * * @return bool */ public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ if($target->getId() === Block::OBSIDIAN and $player->getServer()->netherEnabled){ $tx = $target->getX(); $ty = $target->getY(); $tz = $target->getZ(); $x_max = $tx; $x_min = $tx; for($x = $tx + 1; $level->getBlock($this->temporalVector->setComponents($x, $ty, $tz))->getId() == Block::OBSIDIAN; $x++){ $x_max++; } for($x = $tx - 1; $level->getBlock($this->temporalVector->setComponents($x, $ty, $tz))->getId() == Block::OBSIDIAN; $x--){ $x_min--; } $count_x = $x_max - $x_min + 1; if($count_x >= 4 and $count_x <= 23){ $x_max_y = $ty; $x_min_y = $ty; for($y = $ty; $level->getBlock($this->temporalVector->setComponents($x_max, $y, $tz))->getId() == Block::OBSIDIAN; $y++){ $x_max_y++; } for($y = $ty; $level->getBlock($this->temporalVector->setComponents($x_min, $y, $tz))->getId() == Block::OBSIDIAN; $y++){ $x_min_y++; } $y_max = min($x_max_y, $x_min_y) - 1; $count_y = $y_max - $ty + 2; if($count_y >= 5 and $count_y <= 23){ $count_up = 0; for($ux = $x_min; ($level->getBlock($this->temporalVector->setComponents($ux, $y_max, $tz))->getId() == Block::OBSIDIAN and $ux <= $x_max); $ux++){ $count_up++; } if($count_up == $count_x){ for($px = $x_min + 1; $px < $x_max; $px++){ for($py = $ty + 1; $py < $y_max; $py++){ $level->setBlock($this->temporalVector->setComponents($px, $py, $tz), new Portal()); } } if($player->isSurvival()){ $this->useOn($block, 2); $player->getInventory()->setItemInHand($this); } return true; } } } $z_max = $tz; $z_min = $tz; for($z = $tz + 1; $level->getBlock($this->temporalVector->setComponents($tx, $ty, $z))->getId() == Block::OBSIDIAN; $z++){ $z_max++; } for($z = $tz - 1; $level->getBlock($this->temporalVector->setComponents($tx, $ty, $z))->getId() == Block::OBSIDIAN; $z--){ $z_min--; } $count_z = $z_max - $z_min + 1; if($count_z >= 4 and $count_z <= 23){ $z_max_y = $ty; $z_min_y = $ty; for($y = $ty; $level->getBlock($this->temporalVector->setComponents($tx, $y, $z_max))->getId() == Block::OBSIDIAN; $y++){ $z_max_y++; } for($y = $ty; $level->getBlock($this->temporalVector->setComponents($tx, $y, $z_min))->getId() == Block::OBSIDIAN; $y++){ $z_min_y++; } $y_max = min($z_max_y, $z_min_y) - 1; $count_y = $y_max - $ty + 2; if($count_y >= 5 and $count_y <= 23){ $count_up = 0; for($uz = $z_min; ($level->getBlock($this->temporalVector->setComponents($tx, $y_max, $uz))->getId() == Block::OBSIDIAN and $uz <= $z_max); $uz++){ $count_up++; } if($count_up == $count_z){ for($pz = $z_min + 1; $pz < $z_max; $pz++){ for($py = $ty + 1; $py < $y_max; $py++){ $level->setBlock($this->temporalVector->setComponents($tx, $py, $pz), new Portal()); } } if($player->isSurvival()){ $this->useOn($block, 2); $player->getInventory()->setItemInHand($this); } return true; } } } } if($block->getId() === self::AIR and ($target instanceof Solid)){ $level->setBlock($block, new Fire(), true); /** @var Fire $block */ $block = $level->getBlock($block); if($block->getSide(Vector3::SIDE_DOWN)->isTopFacingSurfaceSolid() or $block->canNeighborBurn()){ $level->scheduleUpdate($block, $block->getTickRate() + mt_rand(0, 10)); } if($player->isSurvival()){ $this->useOn($block, 2); $player->getInventory()->setItemInHand($this); } return true; } return false; } }meta === self::FISH_SALMON){ $name = "Raw Salmon"; }elseif($this->meta === self::FISH_CLOWNFISH){ $name = "Clownfish"; }elseif($this->meta === self::FISH_PUFFERFISH){ $name = "Pufferfish"; } parent::__construct(self::RAW_FISH, $meta, $count, $name); } /** * @return int */ public function getFoodRestore() : int{ if($this->meta === self::FISH_FISH){ return 2; }elseif($this->meta === self::FISH_SALMON){ return 2; }elseif($this->meta === self::FISH_CLOWNFISH){ return 1; }elseif($this->meta === self::FISH_PUFFERFISH){ return 1.2; } return 0; } /** * @return float */ public function getSaturationRestore() : float{ if($this->meta === self::FISH_FISH){ return 0.4; }elseif($this->meta === self::FISH_SALMON){ return 0.4; }elseif($this->meta === self::FISH_CLOWNFISH){ return 0.2; }elseif($this->meta === self::FISH_PUFFERFISH){ return 0.2; } return 0; } /** * @return array */ public function getAdditionalEffects() : array{ return $this->meta === self::FISH_PUFFERFISH ? [ Effect::getEffect(Effect::HUNGER)->setDuration(300)->setAmplifier(2), Effect::getEffect(Effect::NAUSEA)->setDuration(300)->setAmplifier(1), Effect::getEffect(Effect::POISON)->setDuration(1200)->setAmplifier(3), ] : []; } } temporalVector === null){ $this->temporalVector = new Vector3(0, 0, 0); } } /** * @return bool */ public function canBeActivated() : bool{ return true; } /** * @param Level $level * @param Player $player * @param Block $block * @param Block $target * @param $face * @param $fx * @param $fy * @param $fz * * @return bool */ public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ if($target->getId() === Block::OBSIDIAN and $player->getServer()->netherEnabled){//黑曜石 4*5最小 23*23最大 //$level->setBlock($block, new Fire(), true); $tx = $target->getX(); $ty = $target->getY(); $tz = $target->getZ(); //x方向 $x_max = $tx;//x最大值 $x_min = $tx;//x最小值 for($x = $tx + 1; $level->getBlock($this->temporalVector->setComponents($x, $ty, $tz))->getId() == Block::OBSIDIAN; $x++){ $x_max++; } for($x = $tx - 1; $level->getBlock($this->temporalVector->setComponents($x, $ty, $tz))->getId() == Block::OBSIDIAN; $x--){ $x_min--; } $count_x = $x_max - $x_min + 1;//x方向方块 if($count_x >= 4 and $count_x <= 23){//4 23 $x_max_y = $ty;//x最大值时的y最大值 $x_min_y = $ty;//x最小值时的y最大值 for($y = $ty; $level->getBlock($this->temporalVector->setComponents($x_max, $y, $tz))->getId() == Block::OBSIDIAN; $y++){ $x_max_y++; } for($y = $ty; $level->getBlock($this->temporalVector->setComponents($x_min, $y, $tz))->getId() == Block::OBSIDIAN; $y++){ $x_min_y++; } $y_max = min($x_max_y, $x_min_y) - 1;//y最大值 $count_y = $y_max - $ty + 2;//方向方块 //Server::getInstance()->broadcastMessage("$y_max $x_max_y $x_min_y $x_max $x_min"); if($count_y >= 5 and $count_y <= 23){//5 23 $count_up = 0;//上面 for($ux = $x_min; ($level->getBlock($this->temporalVector->setComponents($ux, $y_max, $tz))->getId() == Block::OBSIDIAN and $ux <= $x_max); $ux++){ $count_up++; } //Server::getInstance()->broadcastMessage("$count_up $count_x"); if($count_up == $count_x){ for($px = $x_min + 1; $px < $x_max; $px++){ for($py = $ty + 1; $py < $y_max; $py++){ $level->setBlock($this->temporalVector->setComponents($px, $py, $tz), new Portal()); } } if($player->isSurvival()){ $this->useOn($block, 2); $player->getInventory()->setItemInHand($this); } return true; } } } //z方向 $z_max = $tz;//z最大值 $z_min = $tz;//z最小值 for($z = $tz + 1; $level->getBlock($this->temporalVector->setComponents($tx, $ty, $z))->getId() == Block::OBSIDIAN; $z++){ $z_max++; } for($z = $tz - 1; $level->getBlock($this->temporalVector->setComponents($tx, $ty, $z))->getId() == Block::OBSIDIAN; $z--){ $z_min--; } $count_z = $z_max - $z_min + 1; if($count_z >= 4 and $count_z <= 23){//4 23 $z_max_y = $ty;//z最大值时的y最大值 $z_min_y = $ty;//z最小值时的y最大值 for($y = $ty; $level->getBlock($this->temporalVector->setComponents($tx, $y, $z_max))->getId() == Block::OBSIDIAN; $y++){ $z_max_y++; } for($y = $ty; $level->getBlock($this->temporalVector->setComponents($tx, $y, $z_min))->getId() == Block::OBSIDIAN; $y++){ $z_min_y++; } $y_max = min($z_max_y, $z_min_y) - 1;//y最大值 $count_y = $y_max - $ty + 2;//方向方块 if($count_y >= 5 and $count_y <= 23){//5 23 $count_up = 0;//上面 for($uz = $z_min; ($level->getBlock($this->temporalVector->setComponents($tx, $y_max, $uz))->getId() == Block::OBSIDIAN and $uz <= $z_max); $uz++){ $count_up++; } //Server::getInstance()->broadcastMessage("$count_up $count_z"); if($count_up == $count_z){ for($pz = $z_min + 1; $pz < $z_max; $pz++){ for($py = $ty + 1; $py < $y_max; $py++){ $level->setBlock($this->temporalVector->setComponents($tx, $py, $pz), new Portal()); } } if($player->isSurvival()){ $this->useOn($block, 2); $player->getInventory()->setItemInHand($this); } return true; } } } //return true; } if($block->getId() === self::AIR and ($target instanceof Solid)){ $level->setBlock($block, new Fire(), true); $player->getLevel()->broadcastLevelSoundEvent($player, LevelSoundEventPacket::SOUND_IGNITE); /** @var Fire $block */ $block = $level->getBlock($block); if($block->getSide(Vector3::SIDE_DOWN)->isTopFacingSurfaceSolid() or $block->canNeighborBurn()){ $level->scheduleUpdate($block, $block->getTickRate() + mt_rand(0, 10)); // return true; } if($player->isSurvival()){ $this->useOn($block, 2);//耐久跟报废分别写在 tool 跟 level 了 $player->getInventory()->setItemInHand($this); } return true; } return false; } } block = Block::get(Item::FLOWER_POT_BLOCK); parent::__construct(self::FLOWER_POT, 0, $count, "Flower Pot"); } /** * @return int */ public function getMaxStackSize() : int{ return 64; } } getFood() < $entity->getMaxFood()) and $this->canBeConsumed(); } /** * @return Food|Item */ public function getResidue(){ if($this->getCount() === 1){ return Item::get(0); }else{ $new = clone $this; $new->count--; return $new; } } /** * @return array */ public function getAdditionalEffects() : array{ return []; } /** * @param Entity $human */ public function onConsume(Entity $human){ $pk = new EntityEventPacket(); $pk->eid = $human->getId(); $pk->event = EntityEventPacket::USE_ITEM; if($human instanceof Player){ $human->dataPacket($pk); } $human->getLevel()->getServer()->broadcastPacket($human->getViewers(), $pk); $human->getLevel()->getServer()->getPluginManager()->callEvent($ev = new EntityEatItemEvent($human, $this)); $human->addSaturation($ev->getSaturationRestore()); $human->addFood($ev->getFoodRestore()); foreach($ev->getAdditionalEffects() as $effect){ $human->addEffect($effect); } $human->getInventory()->setItemInHand($ev->getResidue()); } } isSurvival() !== true){ return false; } if($target->getId() === Block::STILL_WATER or $target->getId() === Block::WATER){ $player->getServer()->getPluginManager()->callEvent($ev = new PlayerGlassBottleEvent($player, $target, $this)); if($ev->isCancelled()){ return false; }else{ if($this->count <= 1){ $player->getInventory()->setItemInHand(Item::get(Item::POTION, 0, 1)); return true; }else{ $this->count--; $player->getInventory()->setItemInHand($this); } if($player->getInventory()->canAddItem(Item::get(Item::POTION, 0, 1)) === true){ $player->getInventory()->AddItem(Item::get(Item::POTION, 0, 1)); }else{ $motion = $player->getDirectionVector()->multiply(0.4); $position = clone $player->getPosition(); $player->getLevel()->dropItem($position->add(0, 0.5, 0), Item::get(Item::POTION, 0, 1), $motion, 40); } return true; } } return false; } }canBeConsumed(); } /** * @return int */ public function getFoodRestore() : int{ return 4; } /** * @return float */ public function getSaturationRestore() : float{ return 9.6; } /** * @return array */ public function getAdditionalEffects() : array{ return [ Effect::getEffect(Effect::REGENERATION)->setDuration(100)->setAmplifier(1), Effect::getEffect(Effect::ABSORPTION)->setDuration(2400)->setAmplifier(0) ]; } } block = Block::get(Block::HOPPER_BLOCK); parent::__construct(self::HOPPER, 0, $count, "Hopper"); } }block = Block::get(Item::IRON_DOOR_BLOCK); parent::__construct(self::IRON_DOOR, 0, $count, "Iron Door"); } }read($tag); return self::$cachedParser->getData(); } /** * @param CompoundTag $tag * * @return string */ private static function writeCompoundTag(CompoundTag $tag) : string{ if(self::$cachedParser === null){ self::$cachedParser = new NBT(NBT::LITTLE_ENDIAN); } self::$cachedParser->setData($tag); return self::$cachedParser->write(); } /** @var \SplFixedArray */ public static $list = null; protected $block; protected $id; protected $meta; private $tags = ""; private $cachedNBT = null; public $count; protected $durability = 0; protected $name; /** * @return bool */ public function canBeActivated() : bool{ return false; } /** * @param bool $readFromJson */ public static function init($readFromJson = false){ if(self::$list === null){ //TODO: Sort this mess into some kind of order self::$list = new \SplFixedArray(65536); self::$list[self::SUGARCANE] = Sugarcane::class; self::$list[self::ENDER_PEARL] = EnderPearl::class; self::$list[self::EYE_OF_ENDER] = EyeOfEnder::class; self::$list[self::DRAGONS_BREATH] = DragonsBreath::class; self::$list[self::SHULKER_SHELL] = ShulkerShell::class; self::$list[self::POPPED_CHORUS_FRUIT] = PoppedChorusFruit::class; self::$list[self::WHEAT_SEEDS] = WheatSeeds::class; self::$list[self::PUMPKIN_SEEDS] = PumpkinSeeds::class; self::$list[self::MELON_SEEDS] = MelonSeeds::class; self::$list[self::MUSHROOM_STEW] = MushroomStew::class; self::$list[self::RABBIT_STEW] = RabbitStew::class; self::$list[self::BEETROOT_SOUP] = BeetrootSoup::class; self::$list[self::BEETROOT_SEEDS] = BeetrootSeeds::class; self::$list[self::SIGN] = Sign::class; self::$list[self::WOODEN_DOOR] = WoodenDoor::class; self::$list[self::SPRUCE_DOOR] = SpruceDoor::class; self::$list[self::BIRCH_DOOR] = BirchDoor::class; self::$list[self::JUNGLE_DOOR] = JungleDoor::class; self::$list[self::ACACIA_DOOR] = AcaciaDoor::class; self::$list[self::DARK_OAK_DOOR] = DarkOakDoor::class; self::$list[self::BUCKET] = Bucket::class; self::$list[self::IRON_DOOR] = IronDoor::class; self::$list[self::CAKE] = Cake::class; self::$list[self::BED] = Bed::class; self::$list[self::PAINTING] = Painting::class; self::$list[self::COAL] = Coal::class; self::$list[self::APPLE] = Apple::class; self::$list[self::SPAWN_EGG] = SpawnEgg::class; self::$list[self::DIAMOND] = Diamond::class; self::$list[self::STICK] = Stick::class; self::$list[self::SNOWBALL] = Snowball::class; self::$list[self::BOWL] = Bowl::class; self::$list[self::FEATHER] = Feather::class; self::$list[self::BRICK] = Brick::class; self::$list[self::LEATHER_CAP] = LeatherCap::class; self::$list[self::LEATHER_TUNIC] = LeatherTunic::class; self::$list[self::LEATHER_PANTS] = LeatherPants::class; self::$list[self::LEATHER_BOOTS] = LeatherBoots::class; self::$list[self::CHAIN_HELMET] = ChainHelmet::class; self::$list[self::CHAIN_CHESTPLATE] = ChainChestplate::class; self::$list[self::CHAIN_LEGGINGS] = ChainLeggings::class; self::$list[self::CHAIN_BOOTS] = ChainBoots::class; self::$list[self::IRON_HELMET] = IronHelmet::class; self::$list[self::IRON_CHESTPLATE] = IronChestplate::class; self::$list[self::IRON_LEGGINGS] = IronLeggings::class; self::$list[self::IRON_BOOTS] = IronBoots::class; self::$list[self::GOLD_HELMET] = GoldHelmet::class; self::$list[self::GOLD_CHESTPLATE] = GoldChestplate::class; self::$list[self::GOLD_LEGGINGS] = GoldLeggings::class; self::$list[self::GOLD_BOOTS] = GoldBoots::class; self::$list[self::DIAMOND_HELMET] = DiamondHelmet::class; self::$list[self::DIAMOND_CHESTPLATE] = DiamondChestplate::class; self::$list[self::DIAMOND_LEGGINGS] = DiamondLeggings::class; self::$list[self::DIAMOND_BOOTS] = DiamondBoots::class; self::$list[self::IRON_SWORD] = IronSword::class; self::$list[self::IRON_INGOT] = IronIngot::class; self::$list[self::GOLD_INGOT] = GoldIngot::class; self::$list[self::IRON_SHOVEL] = IronShovel::class; self::$list[self::IRON_PICKAXE] = IronPickaxe::class; self::$list[self::IRON_AXE] = IronAxe::class; self::$list[self::IRON_HOE] = IronHoe::class; self::$list[self::DIAMOND_SWORD] = DiamondSword::class; self::$list[self::DIAMOND_SHOVEL] = DiamondShovel::class; self::$list[self::DIAMOND_PICKAXE] = DiamondPickaxe::class; self::$list[self::DIAMOND_AXE] = DiamondAxe::class; self::$list[self::DIAMOND_HOE] = DiamondHoe::class; self::$list[self::GOLD_SWORD] = GoldSword::class; self::$list[self::GOLD_SHOVEL] = GoldShovel::class; self::$list[self::GOLD_PICKAXE] = GoldPickaxe::class; self::$list[self::GOLD_AXE] = GoldAxe::class; self::$list[self::GOLD_HOE] = GoldHoe::class; self::$list[self::STONE_SWORD] = StoneSword::class; self::$list[self::STONE_SHOVEL] = StoneShovel::class; self::$list[self::STONE_PICKAXE] = StonePickaxe::class; self::$list[self::STONE_AXE] = StoneAxe::class; self::$list[self::STONE_HOE] = StoneHoe::class; self::$list[self::WOODEN_SWORD] = WoodenSword::class; self::$list[self::WOODEN_SHOVEL] = WoodenShovel::class; self::$list[self::WOODEN_PICKAXE] = WoodenPickaxe::class; self::$list[self::WOODEN_AXE] = WoodenAxe::class; self::$list[self::WOODEN_HOE] = WoodenHoe::class; self::$list[self::FLINT_STEEL] = FlintSteel::class; self::$list[self::SHEARS] = Shears::class; self::$list[self::BOW] = Bow::class; self::$list[self::RAW_FISH] = Fish::class; self::$list[self::COOKED_FISH] = CookedFish::class; self::$list[self::NETHER_QUARTZ] = NetherQuartz::class; self::$list[self::POTION] = Potion::class; self::$list[self::GLASS_BOTTLE] = GlassBottle::class; self::$list[self::SPLASH_POTION] = SplashPotion::class; self::$list[self::ENCHANTING_BOTTLE] = EnchantingBottle::class; self::$list[self::BOAT] = Boat::class; self::$list[self::MINECART] = Minecart::class; self::$list[self::ARROW] = Arrow::class; self::$list[self::STRING] = ItemString::class; self::$list[self::GUNPOWDER] = Gunpowder::class; self::$list[self::WHEAT] = Wheat::class; self::$list[self::BREAD] = Bread::class; self::$list[self::FLINT] = Flint::class; self::$list[self::FLINT] = Flint::class; self::$list[self::RAW_PORKCHOP] = RawPorkchop::class; self::$list[self::COOKED_PORKCHOP] = CookedPorkchop::class; self::$list[self::GOLDEN_APPLE] = GoldenApple::class; self::$list[self::MINECART] = Minecart::class; self::$list[self::REDSTONE] = Redstone::class; self::$list[self::LEATHER] = Leather::class; self::$list[self::CLAY] = Clay::class; self::$list[self::PAPER] = Paper::class; self::$list[self::BOOK] = Book::class; self::$list[self::SLIMEBALL] = Slimeball::class; self::$list[self::EGG] = Egg::class; self::$list[self::COMPASS] = Compass::class; self::$list[self::CLOCK] = Clock::class; self::$list[self::GLOWSTONE_DUST] = GlowstoneDust::class; self::$list[self::DYE] = Dye::class; self::$list[self::BONE] = Bone::class; self::$list[self::SUGAR] = Sugar::class; self::$list[self::COOKIE] = Cookie::class; self::$list[self::MELON] = Melon::class; self::$list[self::RAW_BEEF] = RawBeef::class; self::$list[self::STEAK] = Steak::class; self::$list[self::RAW_CHICKEN] = RawChicken::class; self::$list[self::COOKED_CHICKEN] = CookedChicken::class; self::$list[self::GOLD_NUGGET] = GoldNugget::class; self::$list[self::EMERALD] = Emerald::class; self::$list[self::ITEM_FRAME] = ItemFrame::class; self::$list[self::FLOWER_POT] = FlowerPot::class; self::$list[self::CARROT] = Carrot::class; self::$list[self::POTATO] = Potato::class; self::$list[self::BAKED_POTATO] = BakedPotato::class; self::$list[self::PUMPKIN_PIE] = PumpkinPie::class; self::$list[self::NETHER_BRICK] = NetherBrick::class; self::$list[self::QUARTZ] = Quartz::class; self::$list[self::BREWING_STAND] = BrewingStand::class; self::$list[self::CAMERA] = Camera::class; self::$list[self::BEETROOT] = Beetroot::class; self::$list[self::SKULL] = Skull::class; self::$list[self::RAW_RABBIT] = RawRabbit::class; self::$list[self::COOKED_RABBIT] = CookedRabbit::class; self::$list[self::GOLDEN_CARROT] = GoldenCarrot::class; self::$list[self::NETHER_WART] = NetherWart::class; self::$list[self::SPIDER_EYE] = SpiderEye::class; self::$list[self::FERMENTED_SPIDER_EYE] = FermentedSpiderEye::class; self::$list[self::BLAZE_POWDER] = BlazePowder::class; self::$list[self::MAGMA_CREAM] = MagmaCream::class; self::$list[self::GLISTERING_MELON] = GlisteringMelon::class; self::$list[self::ENCHANTED_BOOK] = EnchantedBook::class; self::$list[self::REPEATER] = Repeater::class; self::$list[self::CAULDRON] = Cauldron::class; self::$list[self::ROTTEN_FLESH] = RottenFlesh::class; self::$list[self::ENCHANTED_GOLDEN_APPLE] = EnchantedGoldenApple::class; self::$list[self::RAW_MUTTON] = RawMutton::class; self::$list[self::COOKED_MUTTON] = CookedMutton::class; self::$list[self::HOPPER] = Hopper::class; self::$list[self::ELYTRA] = Elytra::class; self::$list[self::NETHER_STAR] = NetherStar::class; self::$list[self::CHORUS_FRUIT] = ChorusFruit::class; self::$list[self::PRISMARINE_CRYSTALS] = PrismarineCrystals::class; self::$list[self::PRISMARINE_SHARD] = PrismarineShard::class; self::$list[self::FIRE_CHARGE] = FireCharge::class; for($i = 0; $i < 256; ++$i){ if(Block::$list[$i] !== null){ self::$list[$i] = Block::$list[$i]; } } } self::initCreativeItems(); } private static $creative = []; private static function initCreativeItems(){ self::clearCreativeItems(); $creativeItems = new Config(Server::getInstance()->getFilePath() . "src/pocketmine/resources/creativeitems.json", Config::JSON, []); foreach($creativeItems->getAll() as $data){ $item = Item::get($data["id"], $data["damage"], $data["count"], $data["nbt"]); if($item->getName() === "Unknown"){ continue; } self::addCreativeItem($item); } } public static function clearCreativeItems(){ Item::$creative = []; } /** * @return array */ public static function getCreativeItems() : array{ return Item::$creative; } /** * @param Item $item */ public static function addCreativeItem(Item $item){ Item::$creative[] = clone $item; } /** * @param Item $item */ public static function removeCreativeItem(Item $item){ $index = self::getCreativeItemIndex($item); if($index !== -1){ unset(Item::$creative[$index]); } } /** * @param Item $item * * @return bool */ public static function isCreativeItem(Item $item) : bool{ foreach(Item::$creative as $i => $d){ if($item->equals($d, !$item->isTool())){ return true; } } return false; } /** * @param $index * * @return Item */ public static function getCreativeItem(int $index){ return isset(Item::$creative[$index]) ? Item::$creative[$index] : null; } /** * @param Item $item * * @return int */ public static function getCreativeItemIndex(Item $item) : int{ foreach(Item::$creative as $i => $d){ if($item->equals($d, !$item->isTool())){ return $i; } } return -1; } /** * @param int $id * @param int $meta * @param int $count * @param string $tags * * @return Item */ public static function get(int $id, int $meta = 0, int $count = 1, string $tags = "") : Item{ try{ $class = self::$list[$id]; if($class === null){ return (new Item($id, $meta, $count))->setCompoundTag($tags); }elseif($id < 256){ return (new ItemBlock(new $class($meta), $meta, $count))->setCompoundTag($tags); }else{ return (new $class($meta, $count))->setCompoundTag($tags); } }catch(\RuntimeException $e){ return (new Item($id, $meta, $count))->setCompoundTag($tags); } } /** * @param string $str * @param bool $multiple * * @return Item[]|Item */ public static function fromString(string $str, bool $multiple = false){ if($multiple === true){ $blocks = []; foreach(explode(",", $str) as $b){ $blocks[] = self::fromString($b, false); } return $blocks; }else{ $b = explode(":", str_replace([" ", "minecraft:"], ["_", ""], trim($str))); if(!isset($b[1])){ $meta = 0; }else{ $meta = $b[1] & 0xFFFF; } if(defined(Item::class . "::" . strtoupper($b[0]))){ $item = self::get(constant(Item::class . "::" . strtoupper($b[0])), $meta); if($item->getId() === self::AIR and strtoupper($b[0]) !== "AIR"){ $item = self::get($b[0] & 0xFFFF, $meta); } }else{ $item = self::get($b[0] & 0xFFFF, $meta); } return $item; } } /** * Item constructor. * * @param int $id * @param int $meta * @param int $count * @param string $name */ public function __construct(int $id, int $meta = 0, int $count = 1, string $name = "Unknown"){ $this->id = $id & 0xffff; $this->meta = $meta !== -1 ? $meta & 0xffff : -1; $this->count = $count; $this->name = $name; if(!isset($this->block) and $this->id <= 0xff and isset(Block::$list[$this->id])){ $this->block = Block::get($this->id, $this->meta); $this->name = $this->block->getName(); } } /** * @param $tags * * @return $this */ public function setCompoundTag($tags){ if($tags instanceof CompoundTag){ $this->setNamedTag($tags); }else{ $this->tags = (string) $tags; $this->cachedNBT = null; } return $this; } /** * @return string */ public function getCompoundTag() : string{ return $this->tags; } /** * @return bool */ public function hasCompoundTag() : bool{ return $this->tags !== ""; } /** * @return bool */ public function hasCustomBlockData() : bool{ if(!$this->hasCompoundTag()){ return false; } $tag = $this->getNamedTag(); if(isset($tag->BlockEntityTag) and $tag->BlockEntityTag instanceof CompoundTag){ return true; } return false; } /** * @return $this */ public function clearCustomBlockData(){ if(!$this->hasCompoundTag()){ return $this; } $tag = $this->getNamedTag(); if(isset($tag->BlockEntityTag) and $tag->BlockEntityTag instanceof CompoundTag){ unset($tag->display->BlockEntityTag); $this->setNamedTag($tag); } return $this; } /** * @param CompoundTag $compound * * @return $this */ public function setCustomBlockData(CompoundTag $compound){ $tags = clone $compound; $tags->setName("BlockEntityTag"); if(!$this->hasCompoundTag()){ $tag = new CompoundTag("", []); }else{ $tag = $this->getNamedTag(); } $tag->BlockEntityTag = $tags; $this->setNamedTag($tag); return $this; } /** * @return null */ public function getCustomBlockData(){ if(!$this->hasCompoundTag()){ return null; } $tag = $this->getNamedTag(); if(isset($tag->BlockEntityTag) and $tag->BlockEntityTag instanceof CompoundTag){ return $tag->BlockEntityTag; } return null; } /** * @return bool */ public function hasEnchantments() : bool{ if(!$this->hasCompoundTag()){ return false; } $tag = $this->getNamedTag(); if(isset($tag->ench)){ $tag = $tag->ench; if($tag instanceof ListTag){ return true; } } return false; } /** * @param $id * * @return Enchantment|null */ public function getEnchantment(int $id){ if(!$this->hasEnchantments()){ return null; } foreach($this->getNamedTag()->ench as $entry){ if($entry["id"] === $id){ $e = Enchantment::getEnchantment($entry["id"]); $e->setLevel($entry["lvl"]); return $e; } } return null; } /** * @param int $id * @param int $level * @param bool $compareLevel * * @return bool */ public function hasEnchantment(int $id, int $level = 1, bool $compareLevel = false) : bool{ if($this->hasEnchantments()){ foreach($this->getEnchantments() as $enchantment){ if($enchantment->getId() == $id){ if($compareLevel){ if($enchantment->getLevel() == $level){ return true; } }else{ return true; } } } } return false; } /** * @param $id * * @return Int level|0(for null) */ public function getEnchantmentLevel(int $id){ if(!$this->hasEnchantments()){ return 0; } foreach($this->getNamedTag()->ench as $entry){ if($entry["id"] === $id){ $e = Enchantment::getEnchantment($entry["id"]); $e->setLevel($entry["lvl"]); $E_level = $e->getLevel() > Enchantment::getEnchantMaxLevel($id) ? Enchantment::getEnchantMaxLevel($id) : $e->getLevel(); return $E_level; } } return 0; } /** * @param Enchantment $ench */ public function addEnchantment(Enchantment $ench){ if(!$this->hasCompoundTag()){ $tag = new CompoundTag("", []); }else{ $tag = $this->getNamedTag(); } if(!isset($tag->ench)){ $tag->ench = new ListTag("ench", []); $tag->ench->setTagType(NBT::TAG_Compound); } $found = false; foreach($tag->ench as $k => $entry){ if($entry["id"] === $ench->getId()){ $tag->ench->{$k} = new CompoundTag("", [ "id" => new ShortTag("id", $ench->getId()), "lvl" => new ShortTag("lvl", $ench->getLevel()) ]); $found = true; break; } } if(!$found){ $count = 0; foreach($tag->ench as $key => $value){ if(is_numeric($key)){ $count++; } } $tag->ench->{$count + 1} = new CompoundTag("", [ "id" => new ShortTag("id", $ench->getId()), "lvl" => new ShortTag("lvl", $ench->getLevel()) ]); } $this->setNamedTag($tag); } /** * @return Enchantment[] */ public function getEnchantments() : array{ if(!$this->hasEnchantments()){ return []; } $enchantments = []; foreach($this->getNamedTag()->ench as $entry){ $e = Enchantment::getEnchantment($entry["id"]); $e->setLevel($entry["lvl"]); $enchantments[] = $e; } return $enchantments; } /** * @return bool */ public function hasRepairCost() : bool{ if(!$this->hasCompoundTag()){ return false; } $tag = $this->getNamedTag(); if(isset($tag->RepairCost)){ $tag = $tag->RepairCost; if($tag instanceof IntTag){ return true; } } return false; } /** * @return int */ public function getRepairCost() : int{ if(!$this->hasCompoundTag()){ return 1; } $tag = $this->getNamedTag(); if(isset($tag->display)){ $tag = $tag->RepairCost; if($tag instanceof IntTag){ return $tag->getValue(); } } return 1; } /** * @param int $cost * * @return $this */ public function setRepairCost(int $cost){ if($cost === 1){ $this->clearRepairCost(); } if(!($hadCompoundTag = $this->hasCompoundTag())){ $tag = new CompoundTag("", []); }else{ $tag = $this->getNamedTag(); } $tag->RepairCost = new IntTag("RepairCost", $cost); if(!$hadCompoundTag){ $this->setCompoundTag($tag); } return $this; } /** * @return $this */ public function clearRepairCost(){ if(!$this->hasCompoundTag()){ return $this; } $tag = $this->getNamedTag(); if(isset($tag->RepairCost) and $tag->RepairCost instanceof IntTag){ unset($tag->RepairCost); $this->setNamedTag($tag); } return $this; } /** * @return bool */ public function hasCustomName() : bool{ if(!$this->hasCompoundTag()){ return false; } $tag = $this->getNamedTag(); if(isset($tag->display)){ $tag = $tag->display; if($tag instanceof CompoundTag and isset($tag->Name) and $tag->Name instanceof StringTag){ return true; } } return false; } /** * @return string */ public function getCustomName() : string{ if(!$this->hasCompoundTag()){ return ""; } $tag = $this->getNamedTag(); if(isset($tag->display)){ $tag = $tag->display; if($tag instanceof CompoundTag and isset($tag->Name) and $tag->Name instanceof StringTag){ return $tag->Name->getValue(); } } return ""; } /** * @param string $name * * @return $this */ public function setCustomName(string $name){ if($name === ""){ $this->clearCustomName(); } if(!($hadCompoundTag = $this->hasCompoundTag())){ $tag = new CompoundTag("", []); }else{ $tag = $this->getNamedTag(); } if(isset($tag->display) and $tag->display instanceof CompoundTag){ $tag->display->Name = new StringTag("Name", $name); }else{ $tag->display = new CompoundTag("display", [ "Name" => new StringTag("Name", $name) ]); } if(!$hadCompoundTag){ $this->setCompoundTag($tag); } return $this; } /** * @return $this */ public function clearCustomName(){ if(!$this->hasCompoundTag()){ return $this; } $tag = $this->getNamedTag(); if(isset($tag->display) and $tag->display instanceof CompoundTag){ unset($tag->display->Name); if($tag->display->getCount() === 0){ unset($tag->display); } $this->setNamedTag($tag); } return $this; } /** * @return array */ public function getLore() : array{ $tag = $this->getNamedTagEntry("display"); if($tag instanceof CompoundTag and isset($tag->Lore) and $tag->Lore instanceof ListTag){ $lines = []; foreach($tag->Lore->getValue() as $line){ $lines[] = $line->getValue(); } return $lines; } return []; } /** * @param array $lines * * @return $this */ public function setLore(array $lines){ $tag = $this->getNamedTag() ?? new CompoundTag("", []); if(!isset($tag->display)){ $tag->display = new CompoundTag("display", []); } $tag->display->Lore = new ListTag("Lore"); $tag->display->Lore->setTagType(NBT::TAG_String); $count = 0; foreach($lines as $line){ $tag->display->Lore[$count++] = new StringTag("", $line); } $this->setNamedTag($tag); return $this; } /** * @param $name * * @return null */ public function getNamedTagEntry($name){ $tag = $this->getNamedTag(); if($tag !== null){ return isset($tag->{$name}) ? $tag->{$name} : null; } return null; } /** * @return null|CompoundTag */ public function getNamedTag(){ if(!$this->hasCompoundTag()){ return null; }elseif($this->cachedNBT !== null){ return $this->cachedNBT; } return $this->cachedNBT = self::parseCompoundTag($this->tags); } /** * @param CompoundTag $tag * * @return $this|Item */ public function setNamedTag(CompoundTag $tag){ if($tag->getCount() === 0){ return $this->clearNamedTag(); } $this->cachedNBT = $tag; $this->tags = self::writeCompoundTag($tag); return $this; } /** * @return Item */ public function clearNamedTag(){ return $this->setCompoundTag(""); } /** * @return int */ public function getCount() : int{ return $this->count; } /** * @param int $count */ public function setCount(int $count){ $this->count = $count; } /** * @return string */ final public function getName() : string{ return $this->hasCustomName() ? $this->getCustomName() : $this->name; } /** * @return bool */ final public function canBePlaced() : bool{ return $this->block !== null and $this->block->canBePlaced(); } /** * @return bool */ final public function isPlaceable() : bool{ return $this->canBePlaced(); } /** * @return bool */ public function canBeConsumed() : bool{ return false; } /** * @param Entity $entity * * @return bool */ public function canBeConsumedBy(Entity $entity) : bool{ return $this->canBeConsumed(); } /** * @param Entity $entity */ public function onConsume(Entity $entity){ } /** * @return Block */ public function getBlock() : Block{ if($this->block instanceof Block){ return clone $this->block; }else{ return Block::get(self::AIR); } } /** * @return int */ final public function getId() : int{ return $this->id; } /** * @return int */ final public function getDamage() : int{ return $this->meta; } /** * @param int $meta */ public function setDamage(int $meta){ $this->meta = $meta !== -1 ? $meta & 0xFFFF : -1; } /** * @return bool */ public function hasAnyDamageValue() : bool{ return $this->meta === -1; } /** * @return int */ public function getMaxStackSize() : int{ return 64; } /** * @return null */ final public function getFuelTime(){ if(!isset(Fuel::$duration[$this->id])){ return null; } if($this->id !== self::BUCKET or $this->meta === 10){ return Fuel::$duration[$this->id]; } return null; } /** * @param Entity|Block $object * * @return bool */ public function useOn($object){ return false; } /** * @return bool */ public function isTool(){ return false; } /** * @return int|bool */ public function getMaxDurability(){ return false; } /** * @return bool */ public function isPickaxe(){ return false; } /** * @return bool */ public function isAxe(){ return false; } /** * @return bool */ public function isSword(){ return false; } /** * @return bool */ public function isShovel(){ return false; } /** * @return bool */ public function isHoe(){ return false; } /** * @return bool */ public function isShears(){ return false; } /** * @return bool */ public function isArmor(){ return false; } /** * @return bool */ public function getArmorValue(){ return false; } /** * @return bool */ public function isBoots(){ return false; } /** * @return bool */ public function isHelmet(){ return false; } /** * @return bool */ public function isLeggings(){ return false; } /** * @return bool */ public function isChestplate(){ return false; } /** * @return int */ public function getAttackDamage(){ return 1; } /** * @param Entity $target * * @return float|int */ public function getModifyAttackDamage(Entity $target){ $rec = $this->getAttackDamage(); $sharpL = $this->getEnchantmentLevel(Enchantment::TYPE_WEAPON_SHARPNESS); if($sharpL > 0){ $rec += 0.5 * ($sharpL + 1); } if($target instanceof Skeleton or $target instanceof Zombie or $target instanceof Witch or $target instanceof PigZombie ){ //SMITE wither skeletons $rec += 2.5 * $this->getEnchantmentLevel(Enchantment::TYPE_WEAPON_SMITE); }elseif($target instanceof Spider or $target instanceof CaveSpider or $target instanceof Silverfish ){ //Bane of Arthropods wither skeletons $rec += 2.5 * $this->getEnchantmentLevel(Enchantment::TYPE_WEAPON_ARTHROPODS); } return $rec; } /** * @param Block $block * @param Player $player * * @return int */ public function getDestroySpeed(Block $block, Player $player){ return 1; } /** * @param Level $level * @param Player $player * @param Block $block * @param Block $target * @param $face * @param $fx * @param $fy * @param $fz * * @return bool */ public function onActivate(Level $level, Player $player, Block $block, Block $target, $face, $fx, $fy, $fz){ return false; } /** * @param Item $item * @param bool $checkDamage * @param bool $checkCompound * @param bool $checkCount * * @return bool */ public final function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true, $checkCount = false) : bool{ if($this->id === $item->getId() and ($checkDamage === false or $this->getDamage() === $item->getDamage()) and ($checkCount === false or $this->getCount() === $item->getCount())){ if($checkCompound){ if($item->getCompoundTag() === $this->getCompoundTag()){ return true; }elseif($this->hasCompoundTag() and $item->hasCompoundTag()){ //Serialized NBT didn't match, check the cached object tree. return NBT::matchTree($this->getNamedTag(), $item->getNamedTag()); } }else{ return true; } } return false; } /** * @return string */ final public function __toString() : string{ return "Item " . $this->name . " (" . $this->id . ":" . ($this->meta === null ? "?" : $this->meta) . ")x" . $this->count . ($this->hasCompoundTag() ? " tags:0x" . bin2hex($this->getCompoundTag()) : ""); } /** * @return array */ final public function jsonSerialize(){ return [ "id" => $this->id, "damage" => $this->meta, "count" => $this->count, //TODO: separate items and stacks "nbt" => $this->tags ]; } /** * Serializes the item to an NBT CompoundTag * * @param int $slot optional, the inventory slot of the item * @param string $tagName the name to assign to the CompoundTag object * * @return CompoundTag */ public function nbtSerialize(int $slot = -1, string $tagName = "") : CompoundTag{ $tag = new CompoundTag($tagName, [ "id" => new ShortTag("id", $this->id), "Count" => new ByteTag("Count", $this->count ?? -1), "Damage" => new ShortTag("Damage", $this->meta), ]); if($this->hasCompoundTag()){ $tag->tag = clone $this->getNamedTag(); $tag->tag->setName("tag"); } if($slot !== -1){ $tag->Slot = new ByteTag("Slot", $slot); } return $tag; } /** * Deserializes an Item from an NBT CompoundTag * * @param CompoundTag $tag * * @return Item */ public static function nbtDeserialize(CompoundTag $tag) : Item{ if(!isset($tag->id) or !isset($tag->Count)){ return Item::get(0); } if($tag->id instanceof ShortTag){ $item = Item::get($tag->id->getValue(), !isset($tag->Damage) ? 0 : $tag->Damage->getValue(), $tag->Count->getValue()); }elseif($tag->id instanceof StringTag){ //PC item save format $item = Item::fromString($tag->id->getValue()); $item->setDamage(!isset($tag->Damage) ? 0 : $tag->Damage->getValue()); $item->setCount($tag->Count->getValue()); }else{ throw new \InvalidArgumentException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($tag->id) . " given"); } if(isset($tag->tag) and $tag->tag instanceof CompoundTag){ $item->setNamedTag($tag->tag); } return $item; } } block = $block; parent::__construct($block->getId(), $block->getDamage(), $count, $block->getName()); } /** * @param int $meta */ public function setDamage(int $meta){ $this->meta = $meta !== -1 ? $meta & 0xf : -1; $this->block->setDamage($this->meta !== -1 ? $this->meta : 0); } public function __clone(){ $this->block = clone $this->block; } /** * @return Block */ public function getBlock() : Block{ return $this->block; } }block = Block::get(Item::ITEM_FRAME_BLOCK); parent::__construct(self::ITEM_FRAME, 0, $count, "Item Frame"); } }block = Block::get(Item::JUNGLE_DOOR_BLOCK); parent::__construct(self::JUNGLE_DOOR, 0, $count, "Jungle Door"); } }block = Block::get(Item::MELON_STEM); parent::__construct(self::MELON_SEEDS, 0, $count, "Melon Seeds"); } }getLevel(), new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $block->getX()), new DoubleTag("", $block->getY() + 0.8), new DoubleTag("", $block->getZ()) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), ])); $minecart->spawnToAll(); if($player->isSurvival()){ $item = $player->getInventory()->getItemInHand(); $count = $item->getCount(); if(--$count <= 0){ $player->getInventory()->setItemInHand(Item::get(Item::AIR)); return true; } $item->setCount($count); $player->getInventory()->setItemInHand($item); } return true; } } block = Block::get(Item::NETHER_WART_BLOCK); parent::__construct(self::NETHER_WART, $meta, $count, "Nether Wart"); } } isTransparent() === false and $face > 1 and $block->isSolid() === false){ $faces = [ 2 => 1, 3 => 3, 4 => 0, 5 => 2, ]; $motives = [ // Motive Width Height ["Kebab", 1, 1], ["Aztec", 1, 1], ["Alban", 1, 1], ["Aztec2", 1, 1], ["Bomb", 1, 1], ["Plant", 1, 1], ["Wasteland", 1, 1], ["Wanderer", 1, 2], ["Graham", 1, 2], ["Pool", 2, 1], ["Courbet", 2, 1], ["Sunset", 2, 1], ["Sea", 2, 1], ["Creebet", 2, 1], ["Match", 2, 2], ["Bust", 2, 2], ["Stage", 2, 2], ["Void", 2, 2], ["SkullAndRoses", 2, 2], //array("Wither", 2, 2), ["Fighters", 4, 2], ["Skeleton", 4, 3], ["DonkeyKong", 4, 3], ["Pointer", 4, 4], ["Pigscene", 4, 4], ["Flaming Skull", 4, 4], ]; $right = [4, 5, 3, 2]; $validMotives = []; foreach($motives as $motive){ $valid = true; for($x = 0; $x < $motive[1] && $valid; $x++){ for($z = 0; $z < $motive[2] && $valid; $z++){ if($target->getSide($right[$face - 2], $x)->isTransparent() || $target->getSide(Vector3::SIDE_UP, $z)->isTransparent() || $block->getSide($right[$face - 2], $x)->isSolid() || $block->getSide(Vector3::SIDE_UP, $z)->isSolid() ){ $valid = false; } } } if($valid){ $validMotives[] = $motive; } } $motive = $motives[mt_rand(0, count($validMotives) - 1)]; $data = [ "x" => $target->x, "y" => $target->y, "z" => $target->z, "yaw" => $faces[$face] * 90, "Motive" => $motive[0], ]; $nbt = new CompoundTag("", [ "Motive" => new StringTag("Motive", $data["Motive"]), "Pos" => new ListTag("Pos", [ new DoubleTag("", $data["x"]), new DoubleTag("", $data["y"]), new DoubleTag("", $data["z"]) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", $data["yaw"]), new FloatTag("", 0) ]), ]); $painting = new PaintingEntity($player->getLevel(), $nbt); $painting->spawnToAll(); if($player->isSurvival()){ $item = $player->getInventory()->getItemInHand(); $count = $item->getCount(); if(--$count <= 0){ $player->getInventory()->setItemInHand(Item::get(Item::AIR)); return true; } $item->setCount($count); $player->getInventory()->setItemInHand($item); } //TODO //$e = $server->api->entity->add($level, ENTITY_OBJECT, OBJECT_PAINTING, $data); //$e->spawnToAll(); /*if(($player->gamemode & 0x01) === 0x00){ $player->removeItem(Item::get($this->getId(), $this->getDamage(), 1)); }*/ return true; } return false; } }block = Block::get(Item::POTATO_BLOCK); parent::__construct(self::POTATO, 0, $count, "Potato"); } } [matching effect, duration in ticks, amplifier] //Use false if no effects. const POTIONS = [ self::WATER_BOTTLE => false, self::MUNDANE => false, self::MUNDANE_EXTENDED => false, self::THICK => false, self::AWKWARD => false, self::NIGHT_VISION => [Effect::NIGHT_VISION, (180 * 20), 0], self::NIGHT_VISION_T => [Effect::NIGHT_VISION, (480 * 20), 0], self::INVISIBILITY => [Effect::INVISIBILITY, (180 * 20), 0], self::INVISIBILITY_T => [Effect::INVISIBILITY, (480 * 20), 0], self::LEAPING => [Effect::JUMP, (180 * 20), 0], self::LEAPING_T => [Effect::JUMP, (480 * 20), 0], self::LEAPING_TWO => [Effect::JUMP, (90 * 20), 1], self::FIRE_RESISTANCE => [Effect::FIRE_RESISTANCE, (180 * 20), 0], self::FIRE_RESISTANCE_T => [Effect::FIRE_RESISTANCE, (480 * 20), 0], self::SWIFTNESS => [Effect::SPEED, (180 * 20), 0], self::SWIFTNESS_T => [Effect::SPEED, (480 * 20), 0], self::SWIFTNESS_TWO => [Effect::SPEED, (90 * 20), 1], self::SLOWNESS => [Effect::SLOWNESS, (90 * 20), 0], self::SLOWNESS_T => [Effect::SLOWNESS, (240 * 20), 0], self::WATER_BREATHING => [Effect::WATER_BREATHING, (180 * 20), 0], self::WATER_BREATHING_T => [Effect::WATER_BREATHING, (480 * 20), 0], self::HEALING => [Effect::HEALING, (1), 0], self::HEALING_TWO => [Effect::HEALING, (1), 1], self::HARMING => [Effect::HARMING, (1), 0], self::HARMING_TWO => [Effect::HARMING, (1), 1], self::POISON => [Effect::POISON, (45 * 20), 0], self::POISON_T => [Effect::POISON, (120 * 20), 0], self::POISON_TWO => [Effect::POISON, (22 * 20), 1], self::REGENERATION => [Effect::REGENERATION, (45 * 20), 0], self::REGENERATION_T => [Effect::REGENERATION, (120 * 20), 0], self::REGENERATION_TWO => [Effect::REGENERATION, (22 * 20), 1], self::STRENGTH => [Effect::STRENGTH, (180 * 20), 0], self::STRENGTH_T => [Effect::STRENGTH, (480 * 20), 0], self::STRENGTH_TWO => [Effect::STRENGTH, (90 * 20), 1], self::WEAKNESS => [Effect::WEAKNESS, (90 * 20), 0], self::WEAKNESS_T => [Effect::WEAKNESS, (240 * 20), 0] ]; /** * Potion constructor. * * @param int $meta * @param int $count */ public function __construct($meta = 0, $count = 1){ parent::__construct(self::POTION, $meta, $count, self::getNameByMeta($meta)); } /** * @param int $meta * * @return array */ public static function getColor(int $meta){ $effect = Effect::getEffect(self::getEffectId($meta)); if($effect !== null){ return $effect->getColor(); } return [0, 0, 0]; } /** * @return int */ public function getMaxStackSize() : int{ return 1; } /** * @return bool */ public function canBeConsumed() : bool{ return $this->meta > 0; } /** * @param Entity $entity * * @return bool */ public function canBeConsumedBy(Entity $entity) : bool{ return $entity instanceof Human; } /** * @return array */ public function getEffects() : array{ return self::getEffectsById($this->meta); } /** * @param int $id * * @return Effect[] */ public static function getEffectsById(int $id) : array{ if(count(self::POTIONS[$id] ?? []) === 3){ return [Effect::getEffect(self::POTIONS[$id][0])->setDuration(self::POTIONS[$id][1])->setAmplifier(self::POTIONS[$id][2])]; } return []; } /** * @param Entity $human */ public function onConsume(Entity $human){ $pk = new EntityEventPacket(); $pk->eid = $human->getId(); $pk->event = EntityEventPacket::USE_ITEM; if($human instanceof Player){ $human->dataPacket($pk); } $server = $human->getLevel()->getServer(); $server->broadcastPacket($human->getViewers(), $pk); $server->getPluginManager()->callEvent($ev = new EntityDrinkPotionEvent($human, $this)); if(!$ev->isCancelled()){ foreach($ev->getEffects() as $effect){ $human->addEffect($effect); } //Don't set the held item to glass bottle if we're in creative if($human instanceof Player){ if($human->getGamemode() === 1){ return; } } $human->getInventory()->setItemInHand(Item::get(self::GLASS_BOTTLE)); } } /** * @param int $meta * * @return int */ public static function getEffectId(int $meta) : int{ switch($meta){ case self::INVISIBILITY: case self::INVISIBILITY_T: return Effect::INVISIBILITY; case self::LEAPING: case self::LEAPING_T: case self::LEAPING_TWO: return Effect::JUMP; case self::FIRE_RESISTANCE: case self::FIRE_RESISTANCE_T: return Effect::FIRE_RESISTANCE; case self::SWIFTNESS: case self::SWIFTNESS_T: case self::SWIFTNESS_TWO: return Effect::SPEED; case self::SLOWNESS: case self::SLOWNESS_T: return Effect::SLOWNESS; case self::WATER_BREATHING: case self::WATER_BREATHING_T: return Effect::WATER_BREATHING; case self::HARMING: case self::HARMING_TWO: return Effect::HARMING; case self::POISON: case self::POISON_T: case self::POISON_TWO: return Effect::POISON; case self::HEALING: case self::HEALING_TWO: return Effect::HEALING; case self::NIGHT_VISION: case self::NIGHT_VISION_T: return Effect::NIGHT_VISION; case self::REGENERATION: case self::REGENERATION_T: case self::REGENERATION_TWO: return Effect::REGENERATION; default: return 0; } } /** * @param int $meta * * @return string */ public static function getNameByMeta(int $meta) : string{ switch($meta){ case self::WATER_BOTTLE: return "Water Bottle"; case self::MUNDANE: case self::MUNDANE_EXTENDED: return "Mundane Potion"; case self::THICK: return "Thick Potion"; case self::AWKWARD: return "Awkward Potion"; case self::INVISIBILITY: case self::INVISIBILITY_T: return "Potion of Invisibility"; case self::LEAPING: case self::LEAPING_T: return "Potion of Leaping"; case self::LEAPING_TWO: return "Potion of Leaping II"; case self::FIRE_RESISTANCE: case self::FIRE_RESISTANCE_T: return "Potion of Fire Resistance"; case self::SWIFTNESS: case self::SWIFTNESS_T: return "Potion of Swiftness"; case self::SWIFTNESS_TWO: return "Potion of Swiftness II"; case self::SLOWNESS: case self::SLOWNESS_T: return "Potion of Slowness"; case self::WATER_BREATHING: case self::WATER_BREATHING_T: return "Potion of Water Breathing"; case self::HARMING: return "Potion of Harming"; case self::HARMING_TWO: return "Potion of Harming II"; case self::POISON: case self::POISON_T: return "Potion of Poison"; case self::POISON_TWO: return "Potion of Poison II"; case self::HEALING: return "Potion of Healing"; case self::HEALING_TWO: return "Potion of Healing II"; case self::NIGHT_VISION: case self::NIGHT_VISION_T: return "Potion of Night Vision"; case self::STRENGTH: case self::STRENGTH_T: return "Potion of Strength"; case self::STRENGTH_TWO: return "Potion of Strength II"; case self::REGENERATION: case self::REGENERATION_T: return "Potion of Regeneration"; case self::REGENERATION_TWO: return "Potion of Regeneration II"; case self::WEAKNESS: case self::WEAKNESS_T: return "Potion of Weakness"; default: return "Potion"; } } } block = Block::get(Item::PUMPKIN_STEM); parent::__construct(self::PUMPKIN_SEEDS, 0, $count, "Pumpkin Seeds"); } }= 70){ return [Effect::getEffect(Effect::HUNGER)->setDuration(30 * 20)]; }else{ return []; } } } block = Block::get(Item::REDSTONE_WIRE); parent::__construct(self::REDSTONE, 0, $count, "Redstone"); } } block = Block::get(Block::UNPOWERED_REPEATER_BLOCK); parent::__construct(self::REPEATER, $meta, $count, "Repeater"); } }= 20){ return [Effect::getEffect(Effect::HUNGER)->setDuration(30 * 20)]; }else{ return []; } } }block = Block::get(Item::SIGN_POST); parent::__construct(self::SIGN, 0, $count, "Sign"); } /** * @return int */ public function getMaxStackSize() : int{ return 16; } }block = Block::get(Block::SKULL_BLOCK); parent::__construct(self::SKULL, $meta, $count, "Skull"); } /** * @return int */ public function getMaxStackSize() : int{ return 64; } }getId() == Block::MONSTER_SPAWNER){ return true; }else{ $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $block->getX() + 0.5), new DoubleTag("", $block->getY()), new DoubleTag("", $block->getZ() + 0.5) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", lcg_value() * 360), new FloatTag("", 0) ]), ]); if($this->hasCustomName()){ $nbt->CustomName = new StringTag("CustomName", $this->getCustomName()); } $entity = Entity::createEntity($this->meta, $level, $nbt); if($entity instanceof Entity){ if($player->isSurvival()){ --$this->count; } $entity->spawnToAll(); return true; } return false; } } }setDuration(80)]; } } getNameByMeta($meta)); } /** * @return int */ public function getMaxStackSize() : int{ return 1; } /** * @param int $meta * * @return string */ public function getNameByMeta(int $meta){ return "Splash " . Potion::getNameByMeta($meta); } }block = Block::get(Item::SPRUCE_DOOR_BLOCK); parent::__construct(self::SPRUCE_DOOR, 0, $count, "Spruce Door"); } }block = Block::get(Item::SUGARCANE_BLOCK); parent::__construct(self::SUGARCANE, 0, $count, "Sugar Cane"); } }isUnbreakable()){ return true; } $unbreakingl = $this->getEnchantmentLevel(Enchantment::TYPE_MINING_DURABILITY); $unbreakingl = $unbreakingl > 3 ? 3 : $unbreakingl; if(mt_rand(1, $unbreakingl + 1) !== 1){ return true; } if($type === 1){ if($object instanceof Entity){ if($this->isHoe() !== false or $this->isSword() !== false){ //Hoe and Sword $this->meta++; return true; }elseif($this->isPickaxe() !== false or $this->isAxe() !== false or $this->isShovel() !== false){ //Pickaxe Axe and Shovel $this->meta += 2; return true; } return true;//Other tool do not lost durability white hitting }elseif($object instanceof Block){ if($this->isShears() !== false){ if($object->getToolType() === Tool::TYPE_SHEARS){//This should be checked in each block $this->meta++; } return true; }elseif($object->getHardness() > 0){//Sword Pickaxe Axe and Shovel if($this->isSword() !== false){ $this->meta += 2; return true; }elseif($this->isPickaxe() !== false or $this->isAxe() !== false or $this->isShovel() !== false){ $this->meta += 1; return true; } } } }elseif($type === 2){//For Touch. only trigger when OnActivate return true if($this->isHoe() !== false or $this->id === self::FLINT_STEEL or $this->isShovel() !== false){ $this->meta++; return true; } } return true; } /** * TODO: Move this to each item * * @return int|bool */ public function getMaxDurability(){ $levels = [ Tool::TIER_GOLD => 33, Tool::TIER_WOODEN => 60, Tool::TIER_STONE => 132, Tool::TIER_IRON => 251, Tool::TIER_DIAMOND => 1562, self::FLINT_STEEL => 65, self::SHEARS => 239, self::BOW => 385, ]; if(($type = $this->isPickaxe()) === false){ if(($type = $this->isAxe()) === false){ if(($type = $this->isSword()) === false){ if(($type = $this->isShovel()) === false){ if(($type = $this->isHoe()) === false){ $type = $this->id; } } } } } return $levels[$type]; } /** * @return bool */ public function isUnbreakable(){ $tag = $this->getNamedTagEntry("Unbreakable"); return $tag !== null and $tag->getValue() > 0; } /** * @return bool */ public function isPickaxe(){ return false; } /** * @return bool */ public function isAxe(){ return false; } /** * @return bool */ public function isSword(){ return false; } /** * @return bool */ public function isShovel(){ return false; } /** * @return bool */ public function isHoe(){ return false; } /** * @return bool */ public function isShears(){ return ($this->id === self::SHEARS); } /** * @return bool */ public function isTool(){ return ($this->id === self::FLINT_STEEL or $this->id === self::SHEARS or $this->id === self::BOW or $this->isPickaxe() !== false or $this->isAxe() !== false or $this->isShovel() !== false or $this->isSword() !== false or $this->isHoe() !== false); } } block = Block::get(Item::WHEAT_BLOCK); parent::__construct(self::WHEAT_SEEDS, 0, $count, "Wheat Seeds"); } }block = Block::get(Item::WOODEN_DOOR_BLOCK); parent::__construct(self::WOODEN_DOOR, 0, $count, "Wooden Door"); } }getLogger()->debug("Unable to register enchantment with id $id."); return new Enchantment(self::TYPE_INVALID, "unknown", 0, 0, 0); } self::$enchantments[$id] = new Enchantment($id, $name, $rarity, $activationType, $slot); return new Enchantment($id, $name, $rarity, $activationType, $slot); } /** * @param $name * * @return Enchantment */ public static function getEnchantmentByName($name){ if(defined(Enchantment::class . "::TYPE_" . strtoupper($name))){ return self::getEnchantment(constant(Enchantment::class . "::TYPE_" . strtoupper($name))); }elseif(defined(Enchantment::class . "::TYPE_WEAPON_" . strtoupper($name))){ return self::getEnchantment(constant(Enchantment::class . "::TYPE_WEAPON_" . strtoupper($name))); }elseif(defined(Enchantment::class . "::TYPE_ARMOR_" . strtoupper($name))){ return self::getEnchantment(constant(Enchantment::class . "::TYPE_ARMOR_" . strtoupper($name))); }elseif(defined(Enchantment::class . "::TYPE_MINING_" . strtoupper($name))){ return self::getEnchantment(constant(Enchantment::class . "::TYPE_MINING_" . strtoupper($name))); }elseif(defined(Enchantment::class . "::TYPE_BOW_" . strtoupper($name))){ return self::getEnchantment(constant(Enchantment::class . "::TYPE_BOW_" . strtoupper($name))); }elseif(defined(Enchantment::class . "::TYPE_FISHING_" . strtoupper($name))){ return self::getEnchantment(constant(Enchantment::class . "::TYPE_FISHING_" . strtoupper($name))); }else{ return new Enchantment(self::TYPE_INVALID, "unknown", 0, 0, 0); } } /** * @param Item $item * * @return int */ public static function getEnchantAbility(Item $item){ switch($item->getId()){ case Item::BOOK: case Item::BOW: case Item::FISHING_ROD: return 4; } if($item->isArmor()){ if($item instanceof ChainBoots or $item instanceof ChainChestplate or $item instanceof ChainHelmet or $item instanceof ChainLeggings) return 12; if($item instanceof IronBoots or $item instanceof IronChestplate or $item instanceof IronHelmet or $item instanceof IronLeggings) return 9; if($item instanceof DiamondBoots or $item instanceof DiamondChestplate or $item instanceof DiamondHelmet or $item instanceof DiamondLeggings) return 10; if($item instanceof LeatherBoots or $item instanceof LeatherTunic or $item instanceof LeatherCap or $item instanceof LeatherPants) return 15; if($item instanceof GoldBoots or $item instanceof GoldChestplate or $item instanceof GoldHelmet or $item instanceof GoldLeggings) return 25; } if($item->isTool()){ if($item instanceof WoodenAxe or $item instanceof WoodenHoe or $item instanceof WoodenPickaxe or $item instanceof WoodenShovel or $item instanceof WoodenSword) return 15; if($item instanceof StoneAxe or $item instanceof StoneHoe or $item instanceof StonePickaxe or $item instanceof StoneShovel or $item instanceof StoneSword) return 5; if($item instanceof DiamondAxe or $item instanceof DiamondHoe or $item instanceof DiamondPickaxe or $item instanceof DiamondShovel or $item instanceof DiamondSword) return 10; if($item instanceof IronAxe or $item instanceof IronHoe or $item instanceof IronPickaxe or $item instanceof IronShovel or $item instanceof IronSword) return 14; if($item instanceof GoldAxe or $item instanceof GoldHoe or $item instanceof GoldPickaxe or $item instanceof GoldShovel or $item instanceof GoldSword) return 22; } return 0; } /** * @param int $enchantmentId * * @return int */ public static function getEnchantWeight(int $enchantmentId){ switch($enchantmentId){ case self::TYPE_ARMOR_PROTECTION: return 10; case self::TYPE_ARMOR_FIRE_PROTECTION: return 5; case self::TYPE_ARMOR_FALL_PROTECTION: return 2; case self::TYPE_ARMOR_EXPLOSION_PROTECTION: return 5; case self::TYPE_WATER_BREATHING: return 2; case self::TYPE_WATER_AFFINITY: return 2; case self::TYPE_WEAPON_SHARPNESS: return 10; case self::TYPE_WEAPON_SMITE: return 5; case self::TYPE_WEAPON_ARTHROPODS: return 5; case self::TYPE_WEAPON_KNOCKBACK: return 5; case self::TYPE_WEAPON_FIRE_ASPECT: return 2; case self::TYPE_WEAPON_LOOTING: return 2; case self::TYPE_MINING_EFFICIENCY: return 10; case self::TYPE_MINING_SILK_TOUCH: return 1; case self::TYPE_MINING_DURABILITY: return 5; case self::TYPE_MINING_FORTUNE: return 2; case self::TYPE_BOW_POWER: return 10; case self::TYPE_BOW_KNOCKBACK: return 2; case self::TYPE_BOW_FLAME: return 2; case self::TYPE_BOW_INFINITY: return 1; } return 0; } /** * @param int $enchantmentId * * @return int */ public static function getEnchantMaxLevel(int $enchantmentId){ switch($enchantmentId){ case self::TYPE_ARMOR_PROTECTION: case self::TYPE_ARMOR_FIRE_PROTECTION: case self::TYPE_ARMOR_FALL_PROTECTION: case self::TYPE_ARMOR_EXPLOSION_PROTECTION: case self::TYPE_ARMOR_PROJECTILE_PROTECTION: return 4; case self::TYPE_ARMOR_THORNS: return 3; case self::TYPE_WATER_BREATHING: case self::TYPE_WATER_SPEED: return 3; case self::TYPE_WATER_AFFINITY: return 1; case self::TYPE_WEAPON_SHARPNESS: case self::TYPE_WEAPON_SMITE: case self::TYPE_WEAPON_ARTHROPODS: return 5; case self::TYPE_WEAPON_KNOCKBACK: case self::TYPE_WEAPON_FIRE_ASPECT: return 2; case self::TYPE_WEAPON_LOOTING: return 3; case self::TYPE_MINING_EFFICIENCY: return 5; case self::TYPE_MINING_SILK_TOUCH: return 1; case self::TYPE_MINING_DURABILITY: case self::TYPE_MINING_FORTUNE: return 3; case self::TYPE_BOW_POWER: return 5; case self::TYPE_BOW_KNOCKBACK: return 2; case self::TYPE_BOW_FLAME: case self::TYPE_BOW_INFINITY: return 1; case self::TYPE_FISHING_FORTUNE: case self::TYPE_FISHING_LURE: return 3; } return 999; } private $id; private $level = 1; private $name; private $rarity; private $activationType; private $slot; private $nickname; private $isCustomVar; /** * Enchantment constructor. * * @param $id * @param $name * @param $rarity * @param $activationType * @param $slot * @param string $nickname * @param bool $custom */ public function __construct($id, $name, $rarity, $activationType, $slot, $nickname = "", $custom = false){ $this->id = (int) $id; $this->name = (string) $name; $this->rarity = (int) $rarity; $this->activationType = (int) $activationType; $this->slot = (int) $slot; $this->nickname = $nickname; $this->isCustomVar = $custom; } /** * @return string */ public function getNickName(){ return $this->nickname; } /** * @return bool */ public function isCustom(){ return (bool) $this->isCustomVar; } /** * @return int */ public function getId(){ return $this->id; } /** * @return string */ public function getName() : string{ return $this->name; } /** * @return int */ public function getRarity(){ return $this->rarity; } /** * @return int */ public function getActivationType(){ return $this->activationType; } /** * @return int */ public function getSlot(){ return $this->slot; } /** * @param $slot * * @return bool */ public function hasSlot($slot){ return ($this->slot & $slot) > 0; } /** * @return int */ public function getLevel(){ return $this->level; } /** * @param int $level * * @return $this */ public function setLevel(int $level){ $this->level = $level; return $this; } /** * @param Enchantment $ent * * @return bool */ public function equals(Enchantment $ent){ if($ent->getId() == $this->getId() and $ent->getLevel() == $this->getLevel() and $ent->getActivationType() == $this->getActivationType() and $ent->getRarity() == $this->getRarity()){ return true; } return false; } /** * @return string */ public static function getRandomName(){ $count = mt_rand(3, 6); $set = []; while(count($set) < $count){ $set[] = self::$words[mt_rand(0, count(self::$words) - 1)]; } return implode(" ", $set); } } enchantments = $enchantments; $this->cost = (int) $cost; $this->randomName = $randomName; } /** * @return array|Enchantment[] */ public function getEnchantments(){ return $this->enchantments; } /** * @return int */ public function getCost(){ return $this->cost; } public function getRandomName(){ return $this->randomName; } } [ new Range(1, 21), new Range(12, 32), new Range(23, 43), new Range(34, 54) ], Enchantment::TYPE_ARMOR_FIRE_PROTECTION => [ new Range(10, 22), new Range(18, 30), new Range(26, 38), new Range(34, 46)], Enchantment::TYPE_ARMOR_FALL_PROTECTION => [ new Range(5, 12), new Range(11, 21), new Range(17, 27), new Range(23, 33) ], Enchantment::TYPE_ARMOR_EXPLOSION_PROTECTION => [ new Range(5, 17), new Range(13, 25), new Range(21, 33), new Range(29, 41) ], Enchantment::TYPE_ARMOR_PROJECTILE_PROTECTION => [ new Range(3, 18), new Range(9, 24), new Range(15, 30), new Range(21, 36) ], Enchantment::TYPE_WATER_BREATHING => [ new Range(10, 40), new Range(20, 50), new Range(30, 60) ], Enchantment::TYPE_WATER_AFFINITY => [ new Range(10, 41) ], Enchantment::TYPE_ARMOR_THORNS => [ new Range(10, 60), new Range(30, 80), new Range(50, 100) ], //Weapon Enchantment::TYPE_WEAPON_SHARPNESS => [ new Range(1, 21), new Range(12, 32), new Range(23, 43), new Range(34, 54), new Range(45, 65) ], Enchantment::TYPE_WEAPON_SMITE => [ new Range(5, 25), new Range(13, 33), new Range(21, 41), new Range(29, 49), new Range(37, 57) ], Enchantment::TYPE_WEAPON_ARTHROPODS => [ new Range(5, 25), new Range(13, 33), new Range(21, 41), new Range(29, 49), new Range(37, 57) ], Enchantment::TYPE_WEAPON_KNOCKBACK => [ new Range(5, 55), new Range(25, 75) ], Enchantment::TYPE_WEAPON_FIRE_ASPECT => [ new Range(10, 60), new Range(30, 80) ], Enchantment::TYPE_WEAPON_LOOTING => [ new Range(15, 65), new Range(24, 74), new Range(33, 83) ], //Bow Enchantment::TYPE_BOW_POWER => [ new Range(1, 16), new Range(11, 26), new Range(21, 36), new Range(31, 46), new Range(41, 56) ], Enchantment::TYPE_BOW_KNOCKBACK => [ new Range(12, 37), new Range(32, 57) ], Enchantment::TYPE_BOW_FLAME => [ new Range(20, 50) ], Enchantment::TYPE_BOW_INFINITY => [ new Range(20, 50) ], //Mining Enchantment::TYPE_MINING_EFFICIENCY => [ new Range(1, 51), new Range(11, 61), new Range(21, 71), new Range(31, 81), new Range(41, 91) ], Enchantment::TYPE_MINING_SILK_TOUCH => [ new Range(15, 65) ], Enchantment::TYPE_MINING_DURABILITY => [ new Range(5, 55), new Range(13, 63), new Range(21, 71) ], Enchantment::TYPE_MINING_FORTUNE => [ new Range(15, 55), new Range(24, 74), new Range(33, 83) ], //Fishing Enchantment::TYPE_FISHING_FORTUNE => [ new Range(15, 65), new Range(24, 74), new Range(33, 83) ], Enchantment::TYPE_FISHING_LURE => [ new Range(15, 65), new Range(24, 74), new Range(33, 83) ] ]; } /** * @param Item $item * @param int $modifiedLevel * * @return Enchantment[] */ public static function getPossibleEnchantments(Item $item, int $modifiedLevel){ $result = []; $enchantmentIds = []; if($item->getId() == Item::BOOK){ $enchantmentIds = array_keys(self::$map); }elseif($item->isArmor()){ $enchantmentIds[] = Enchantment::TYPE_ARMOR_PROTECTION; $enchantmentIds[] = Enchantment::TYPE_ARMOR_FIRE_PROTECTION; $enchantmentIds[] = Enchantment::TYPE_ARMOR_EXPLOSION_PROTECTION; $enchantmentIds[] = Enchantment::TYPE_ARMOR_PROJECTILE_PROTECTION; $enchantmentIds[] = Enchantment::TYPE_ARMOR_THORNS; if($item->isBoots()){ $enchantmentIds[] = Enchantment::TYPE_ARMOR_FALL_PROTECTION; } if($item->isHelmet()){ $enchantmentIds[] = Enchantment::TYPE_WATER_BREATHING; $enchantmentIds[] = Enchantment::TYPE_WATER_AFFINITY; } }elseif($item->isSword()){ $enchantmentIds[] = Enchantment::TYPE_WEAPON_SHARPNESS; $enchantmentIds[] = Enchantment::TYPE_WEAPON_SMITE; $enchantmentIds[] = Enchantment::TYPE_WEAPON_ARTHROPODS; $enchantmentIds[] = Enchantment::TYPE_WEAPON_KNOCKBACK; $enchantmentIds[] = Enchantment::TYPE_WEAPON_FIRE_ASPECT; $enchantmentIds[] = Enchantment::TYPE_WEAPON_LOOTING; }elseif($item->isTool()){ $enchantmentIds[] = Enchantment::TYPE_MINING_EFFICIENCY; $enchantmentIds[] = Enchantment::TYPE_MINING_SILK_TOUCH; $enchantmentIds[] = Enchantment::TYPE_MINING_FORTUNE; }elseif($item->getId() == Item::BOW){ $enchantmentIds[] = Enchantment::TYPE_BOW_POWER; $enchantmentIds[] = Enchantment::TYPE_BOW_KNOCKBACK; $enchantmentIds[] = Enchantment::TYPE_BOW_FLAME; $enchantmentIds[] = Enchantment::TYPE_BOW_INFINITY; }elseif($item->getId() == Item::FISHING_ROD){ $enchantmentIds[] = Enchantment::TYPE_FISHING_FORTUNE; $enchantmentIds[] = Enchantment::TYPE_FISHING_LURE; } if($item->isTool() || $item->isArmor()){ $enchantmentIds[] = Enchantment::TYPE_MINING_DURABILITY; } foreach($enchantmentIds as $enchantmentId){ $enchantment = Enchantment::getEnchantment($enchantmentId); $ranges = self::$map[$enchantmentId]; $i = 0; /** @var Range $range */ foreach($ranges as $range){ $i++; if($range->isInRange($modifiedLevel)){ $result[] = $enchantment->setLevel($i); } } } return $result; } } enchantments = new \SplFixedArray($size); } /** * @param $slot * @param EnchantmentEntry $entry */ public function setSlot($slot, EnchantmentEntry $entry){ $this->enchantments[$slot] = $entry; } /** * @param $slot * * @return EnchantmentEntry */ public function getSlot($slot){ return $this->enchantments[$slot]; } /** * @return int */ public function getSize(){ return $this->enchantments->getSize(); } }langName = strtolower($lang); if($path === null){ $path = \pocketmine\PATH . "src/pocketmine/lang/locale/"; } $this->loadLang($path . $this->langName . ".ini", $this->lang); $this->loadLang($path . $fallback . ".ini", $this->fallbackLang); } /** * @return string */ public function getName() : string{ return $this->get("language.name"); } /** * @return string */ public function getLang(){ return $this->langName; } /** * @param $path * @param array $d */ protected function loadLang($path, array &$d){ if(file_exists($path) and strlen($content = file_get_contents($path)) > 0){ foreach(explode("\n", $content) as $line){ $line = trim($line); if($line === "" or $line{0} === "#"){ continue; } $t = explode("=", $line); if(count($t) < 2){ continue; } $key = trim(array_shift($t)); $value = trim(implode("=", $t)); if($value === ""){ continue; } $d[$key] = $value; } } } /** * @param string $str * @param string[] $params * @param string|null $onlyPrefix * * @return string */ public function translateString($str, array $params = [], $onlyPrefix = null){ $baseText = $this->get($str); $baseText = $this->parseTranslation(($baseText !== null and ($onlyPrefix === null or strpos($str, $onlyPrefix) === 0)) ? $baseText : $str, $onlyPrefix); foreach($params as $i => $p){ $baseText = str_replace("{%$i}", $this->parseTranslation((string) $p), $baseText, $onlyPrefix); } return str_replace("%0", "", $baseText); //fixes a client bug where %0 in translation will cause freeze } /** * @param TextContainer $c * * @return mixed|null|string */ public function translate(TextContainer $c){ if($c instanceof TranslationContainer){ $baseText = $this->internalGet($c->getText()); $baseText = $this->parseTranslation($baseText !== null ? $baseText : $c->getText()); foreach($c->getParameters() as $i => $p){ $baseText = str_replace("{%$i}", $this->parseTranslation($p), $baseText); } }else{ $baseText = $this->parseTranslation($c->getText()); } return $baseText; } /** * @param $id * * @return mixed|null */ public function internalGet($id){ if(isset($this->lang[$id])){ return $this->lang[$id]; }elseif(isset($this->fallbackLang[$id])){ return $this->fallbackLang[$id]; } return null; } /** * @param $id * * @return mixed */ public function get($id){ if(isset($this->lang[$id])){ return $this->lang[$id]; }elseif(isset($this->fallbackLang[$id])){ return $this->fallbackLang[$id]; } return $id; } /** * @param $text * @param null $onlyPrefix * * @return string */ protected function parseTranslation($text, $onlyPrefix = null){ $newString = ""; $replaceString = null; $len = strlen($text); for($i = 0; $i < $len; ++$i){ $c = $text{$i}; if($replaceString !== null){ $ord = ord($c); if( ($ord >= 0x30 and $ord <= 0x39) // 0-9 or ($ord >= 0x41 and $ord <= 0x5a) // A-Z or ($ord >= 0x61 and $ord <= 0x7a) or // a-z $c === "." or $c === "-" ){ $replaceString .= $c; }else{ if(($t = $this->internalGet(substr($replaceString, 1))) !== null and ($onlyPrefix === null or strpos($replaceString, $onlyPrefix) === 1)){ $newString .= $t; }else{ $newString .= $replaceString; } $replaceString = null; if($c === "%"){ $replaceString = $c; }else{ $newString .= $c; } } }elseif($c === "%"){ $replaceString = $c; }else{ $newString .= $c; } } if($replaceString !== null){ if(($t = $this->internalGet(substr($replaceString, 1))) !== null and ($onlyPrefix === null or strpos($replaceString, $onlyPrefix) === 1)){ $newString .= $t; }else{ $newString .= $replaceString; } } return $newString; } } language_has_been_selected = 您现在选择了简体中文. skip_installer = 您想跳过安装向导吗? welcome_to_pocketmine = 欢迎来到GenisysPro!\n在开始使用您的新服务器之前,您需要接受以下协议\nGenisysPro使用了GPL协议,\n您可以在这个文件夹中找到LICENSE文件。 accept_license = 您接受协议内容吗? you_have_to_accept_the_license = 您要接受LGPL协议才可继续使用GenisysPro setting_up_server_now = 您现在要开始设置您的服务器了。 default_values_info = 如果您希望使用默认设置,请直接按下回车键。 server_properties = 您以后可以在server.properties中修改设置. name_your_server = 给您的服务器起个名字吧: port_warning = 如果这是您第一次设置服务器,尽量不要改变端口。 server_port = 设置你的服务器端口: invalid_port = 服务器端口不正确。 online_mode_info = 联机模式下, 服务器将强制玩家需要 Xbox 登录\n当联机时, 建议让服务器开启此功能 online_mode = 您想启用联机模式? ram_warning = 设置GenisysPro可用的最大内存. server_ram = 分配给服务器的内存(RAM)(MB): gamemode_info = 选择模式: (0)生存模式 或 (1)创造模式 default_gamemode = 默认游戏模式 max_players = 最多在线人数 spawn_protection_info = 出生点保护可以在出生点范围内保护所有方块不被改变。 spawn_protection = 启用出生点保护嘛? level_name = 为您的服务器世界设置一个名称: level_type = 设置您服务器的世界类型: invalid_level_type = 无效的世界类型 announce_player_achievements = 当玩家获得成就时是否公布? op_info = OP是服务器的管理员, 可以执行比普通玩家更多的命令. op_who = OP的用户名是什么? op_warning = 你可以执行\"/op <用户名>\"来添加OP. whitelist_info = 白名单可以只允许在其列表内的玩家加入. whitelist_enable = 您想启用白名单吗? whitelist_warning = 你可以用"/whitelist add <用户名>"把别人加入白名单. query_warning1 = Query可用来获取您服务器数据和登录的玩家. query_warning2 = 如果您禁止了它, 您将不能使用服务器列表. query_disable = 您希望禁用Query请求吗? rcon_info = RCON可用来远程连接到服务器控制台(需要密码). rcon_enable = 您希望启用RCON吗? rcon_password = RCON密码 (您也以后更改它) : usage_info = 匿名数据让我们可以获得全球的PocketMine-MP和它的插件的统计信息. 您可以在 stats.pocketmine.net 查看统计信息. usage_disable = 您希望禁用匿名数据吗? ip_get = 获得你的外部IP和内部IP ip_warning = 您的外部IP是 {{EXTERNAL_IP}} . 您可能需要端口转发到您的内网IP {{INTERNAL_IP}} . ip_confirm = 请确认,如没有问题请按下\"回车\"键 you_have_finished = 您已经成功完成了服务器设置向导. pocketmine_will_start = 现在,尽情使用吧~. 输入 \"/help\" 来看所有可用的命令. pocketmine_plugins = 请查看插件源来添加新的功能, 迷你游戏或者对服务器的高级保护. language_has_been_selected = Deutsch wurde erfolgreich als Sprache ausgewählt. skip_installer = Möchtest du den Einrichtungsassistenten überspringen? welcome_to_pocketmine = Willkommen bei GenisysPro!\nBevor du mit der Einrichtung deines neuen Servers beginnen kannst, musst du die Lizenz akzeptieren.\nGenisysPro ist unter der GPL Lizenz Version 3 und neuer lizenziert, welche du in der Datei LICENSE nachlesen kannst. accept_license = Akzeptierst du die Lizenz? you_have_to_accept_the_license = Um GenisysPro verwenden zu können, musst du der Lizenz zustimmen. setting_up_server_now = Du bist nun dabei, deinen Server einzurichten. default_values_info = Drücke einfach Enter, um die Standardwerte zu übernehmen. server_properties = Du kannst sie auch später in der Datei server.properties ändern. name_your_server = Gib deinem Server einen Namen. port_warning = Ändere nicht den Wert für den Standart-Port, falls dies dein erster Server ist. server_port = Server-Port invalid_port = Ungültiger Server-Port online_mode_info = Im Online-Modus wird der Spieler gezwungen, sich mit seinem Xbox-Account zu authentifizieren.\nEs wird die Aktivierung des Online-Modus empfohlen, falls der Server für externe Spieler geöffnet ist. online_mode = Möchtest du den Online-Modus aktivieren? ram_warning = Der RAM ist die maximale Menge an Arbeitsspeicher, welchen GenisysPro verwenden darf. Es wird ein Wert von 128-256 MB empfohlen. server_ram = Server RAM in MB gamemode_info = Wähle zwischen Kreativmodus (1) oder Überlebensmodus (0) default_gamemode = Standard Spielmodus max_players = Max. Online-Spieler spawn_protection_info = Der Spawnschutz (spawn protection) verhindert das Setzen oder Abbauen von Blöcken im Spawn-Bereich, außer für OPs. spawn_protection = Spawnschutz aktivieren? level_name = Bestimme den Namen der Welt level_type = Lege fest, welchen Welttyp der Server verwenden soll invalid_level_type = Ungültiger Welttyp announce_player_achievements = Sollen die Spieler benachrichtigt werden, wenn ein Erfolg erzielt wird? op_info = Ein OP ist ein Spieler, der Admin ist. OPs können mehr Befehle ausführen als normale Spieler. op_who = OP Spielername (z.B. dein Spielername) op_warning = Du kannst später weitere OP Spieler hinzufügen, indem du /op eingibst. whitelist_info = Mit Aktivierung der Whitelist dürfen nur noch Spieler joinen, die darin enthalten sind. whitelist_enable = Möchtest du die Whitelist aktivieren? whitelist_warning = Du musst die Spieler dann noch zur Whitelist hinzufügen query_warning1 = Query ist ein Protokoll, das von verschiedenen Tools verwendet wird, um Informationen zu deinem Servers und die darauf angemeldeten Spieler abzufragen. query_warning2 = Wenn du es deaktivierst, kannst du keine Serverlisten nutzen. query_disable = Möchtest du Query deaktivieren? rcon_info = RCON ist ein Protokoll, über welches man sich aus der Ferne mit einem Passwort zur Serverkonsole verbinden kann. rcon_enable = Möchtest du RCON aktivieren? rcon_password = RCON Passwort (du kannst es später ändern) usage_info = Die anonymen Nutzungsdaten ermöglichen uns, eine globale Statistik für Pocketmine-MP und dessen Plugins zu generieren. Du kannst sie dir auf stats.pocketmine.net ansehen. usage_disable = Möchtest du die anonymen Nutzungsdaten deaktivieren? ip_get = Deine interne und externe IP-Adresse wird ermittelt ip_warning = Deine externe IP ist {{EXTERNAL_IP}}. Möglicherweise muss eine Port-Weiterleitung zu deiner internen IP {{INTERNAL_IP}} eingerichtet werden. ip_confirm = Stelle sicher, dass du das geprüft hast. Falls du eine Weiterleitung benötigst, aber nicht einrichtest, wird kein externer Spieler joinen können. [Drücke Enter] you_have_finished = Du hast den Einrichtungsassistenten nun erfolgreich abgeschlossen. pocketmine_will_start = GenisysPro wird jetzt gestartet. Gib /help ein, um dir eine Liste verfügbarer Befehle anzeigen zu lassen. pocketmine_plugins = Prüfe auch das Plugin-Repository im Web, um deinem Server weitere Features, Minigames oder erweiterten Schutz hinzuzufügen. language_has_been_selected = English has been correctly selected. skip_installer = Do you want to skip the set-up wizard? welcome_to_pocketmine = Welcome to GenisysPro!\nBefore starting setting up your new server you have to accept the license.\nGenisysPro is licensed under the GPL License,\nthat you can read opening the LICENSE file on this folder. accept_license = Do you accept the License? you_have_to_accept_the_license = You have to accept the GPL license to continue using GenisysPro setting_up_server_now = You are going to set up your server now. default_values_info = If you don't want to change the default value, just press Enter. server_properties = You can edit them later on the server.properties file. name_your_server = Give a name to your server port_warning = Do not change the default port value if this is your first server. server_port = Server port invalid_port = Invalid server port online_mode_info = In online mode, the server will force the user to login via XBOX.\nEnabling online mode is recommended if the server is opened to external players. online_mode = Do you want to enable online mode? ram_warning = The RAM is the maximum amount of memory GenisysPro will use. A value of 128-256 MB is recommended server_ram = Server RAM in MB gamemode_info = Choose between Creative (1) or Survival (0) default_gamemode = Default Game mode max_players = Max. online players spawn_protection_info = The spawn protection disallows placing/breaking blocks in the spawn zone except for OPs spawn_protection = Enable spawn protection? level_name = Set the name of the level level_type = Set the server's level type invalid_level_type = Invalid level type announce_player_achievements = Announce when a player gets an achievement? op_info = An OP is the player admin of the server. OPs can run more commands than normal players op_who = OP player name (example, your game name) op_warning = You will be able to add an OP user later using /op whitelist_info = The white-list only allows players in it to join. whitelist_enable = Do you want to enable the white-list? whitelist_warning = You will have to add the players to the white-list query_warning1 = Query is a protocol used by different tools to get information of your server and players logged in. query_warning2 = If you disable it, you won't be able to use server lists. query_disable = Do you want to disable Query? rcon_info = RCON is a protocol to remote connect with the server console using a password. rcon_enable = Do you want to enable RCON? rcon_password = RCON password (you can change it later) usage_info = The anonymous usage data allows us to calculate global statistics for PocketMine-MP and its plugins. You can view them on stats.pocketmine.net usage_disable = Do you want to disable the anonymous usage? ip_get = Getting your external IP and internal IP ip_warning = Your external IP is {{EXTERNAL_IP}}. You may have to port-forward to your internal IP {{INTERNAL_IP}} ip_confirm = Be sure to check it, if you have to forward and you skip that, no external players will be able to join. [Press Enter] you_have_finished = You have finished the set-up wizard correctly pocketmine_will_start = GenisysPro will now start. Type /help to view the list of available commands. pocketmine_plugins = Check the Plugin Repository to add new features, minigames, or advanced protection to your server language_has_been_selected = Français a été correctement sélectionné. skip_installer = Voulez-vous passer l'assistant d'installation? welcome_to_pocketmine = Bienvenue sur GenisysPro!\nAvant de commencer à paramétrer votre nouveau serveur, vous devez accepter la license d'utilisation.\nGenisysPro est sous licence GPL,\nvous pouvez la lire en ouvrant le fichier LICENSE dans ce dossier. accept_license = Acceptez-vous la Licence? you_have_to_accept_the_license = Vous devez accepter la licence GPL pour continuer à utiliser GenisysPro setting_up_server_now = Vous êtes maintenant prêt à paramétrer votre serveur. default_values_info = Si vous ne voulez pas changer la valeur par défaut, appuyez sur entrée. server_properties = Vous pouvez éditer cela plus tard dans le fichier server.properties. name_your_server = Donnez un nom à votre serveur port_warning = Ne changez pas la valeur par défaut du port si c'est votre premier serveur. server_port = Port du serveur invalid_port = Port du serveur invalide ram_warning = La RAM est au maximum de sa capacité par rapport à la mémoire utilisée par PocketMine-MP. Une valeur de 128-256 MB est recommandée server_ram = RAM du serveur en MB gamemode_info = Choisir entre Créatif (1) ou Survie (0) default_gamemode = Mode de jeu par défaut max_players = Joueurs max. en ligne spawn_protection_info = La protection de spawn désactive le placement/cassement de blocs dans la zone de spawn excepté pour les OPs spawn_protection = Activer la protection de spawn? level_name = Donnez un nom à votre niveau level_type = Définissez le type de niveau du serveur invalid_level_type = Type de niveau invalide announce_player_achievements = Annoncer les succés dans le salon de discussion? op_info = Un OP est un administrateur du serveur. Les OPs peuvent exécuter plus de commandes que les joueurs normaux op_who = Nom de joueur OP (exemple, votre nom de jeu) op_warning = Vous serez en mesure d'ajouter un OP plus tard en utilisant /op whitelist_info = La white-list autorise seulement les joueurs présents sur celle-ci. whitelist_enable = Voulez-vous activer la white-list? whitelist_warning = Vous devrez ajouter les joueurs à la white-list query_warning1 = Query est un protocole utilisé par différents outils pour avoir des informations sur votre serveur et les joueurs connectés. query_warning2 = Si vous le désactivez, vous ne pourrez pas utiliser les listes du serveur. query_disable = Voulez-vous désactiver Query? rcon_info = RCON est un protocole pour se connecter à distance à la console du serveur par un mot de passe. rcon_enable = Voulez-vous activer RCON? rcon_password = Mot de passe RCON (vous pouvez le changer plus tard) usage_info = Les données d'utilisation anonyme vous permettent de calculer les statistiques globales de PocketMine-MP et ses plugins. Vous pouvez les voir sur stats.pocketmine.net usage_disable = Voulez-vous désactiver l'utilisation anonyme? ip_get = Obtention de votre IP externe et IP interne ip_warning = Votre IP externe est {{EXTERNAL_IP}}. Vous pourriez avoir à transmettre au port votre IP interne {{INTERNAL_IP}} ip_confirm = Vérifiez-le bien, si vous avancez et sautez cette étape, les joueurs extérieurs ne pourront pas rejoindre votre serveur. [Tapez Enter] you_have_finished = Vous avez correctement terminé l'assistant d'installation pocketmine_will_start = GenisysPro va maintenant démarrer. Tapez /help pour voir la liste des commandes disponibles. pocketmine_plugins = Vérification du répertoire de plugin pour ajouter des nouvelles caractéristiques, mini-jeux, ou une protection avancée de votre serveur language_has_been_selected = Hai selezionato correttamente l'italiano. skip_installer = Vuoi saltare la procedura guidata di installazione? welcome_to_pocketmine = Benvenuto in GenisysPro!\nPrima di iniziare la configurazione del nuovo server e' necessario accettare la licenza.\nGenisysPro e' rilasciato sotto la licenza GPL,\nche si puo' leggere aprendo il file di licenza su questa cartella. accept_license = Accettate la licenza? you_have_to_accept_the_license = Bisogna accettare la licenza GPL per continuare ad utilizzare GenisysPro setting_up_server_now = Si sta per configurare il server ora. default_values_info = Se non si desidera modificare il valore di default, basta premere Invio. server_properties = E' possibile modificarli in seguito aprendo il file server.properties. name_your_server = Nome del server port_warning = Non modificare il valore della porta di default se questo e' il vostro primo server. server_port = Porta del server invalid_port = Porta del server invalida online_mode_info = In modalita' online, il server costringera' l'utente a effettuare il login tramite XBOX. \nL'attivazione della modalita' online e' consigliato se il server e' aperto ai giocatori esterni. online_mode = Vuoi abilitare la modalita' online? ram_warning = La RAM e' la quantita' massima di memoria che GenisysPro utilizzera'. Si consiglia un valore di 128-256 MB server_ram = Server RAM in MB gamemode_info = Scegliere tra Creativa (1) o sopravvivenza (0) default_gamemode = Modalita' di gioco predefinito max_players = Max. giocatori online spawn_protection_info = La protezione di spawn non consente il piazzamento di blocchi e la loro rottura nella zona di spawn ad eccezione degli operatori spawn_protection = Attiva protezione spawn? level_name = Impostare il nome del livello level_type = Impostare il tipo di livello di server invalid_level_type = Tipo di livello non valido announce_player_achievements = Annuncia quando un giocatore ottiene un obbiettivo? op_info = Un operatore e' il giocatore di amministrazione del server. Gli operatori possono eseguire piu' comandi di giocatori normali op_who = Nome delgli operatori (esempio, il tuo nome) op_warning = Sarete in grado di aggiungere un operatore in seguito utilizzando / op whitelist_info = Il white-list consente solo a certi giocatori di unirsi. whitelist_enable = Desideri attivare la white-list? whitelist_warning = Dovrai aggiungere i giocatori alla whitelist query_warning1 = Query e' un protocollo utilizzato da diversi strumenti per ottenere informazioni del server e giocatori loggati. query_warning2 = Se si disattiva, non sarai in grado di utilizzare gli elenchi di server. query_disable = Vuoi disattivare Query? rcon_info = RCON e' un protocollo per la connessione remota con la console del server utilizzando una password. rcon_enable = Vuoi abilitare RCON? rcon_password = password RCON (si puo' cambiare in un secondo momento) usage_info = I dati di utilizzo anonimi ci permette di calcolare le statistiche globali per PocketMine-MP e dei suoi plug-in. e' possibile visualizzarle su stats.pocketmine.net usage_disable = Vuoi per disattivare l'uso di dati anonimo? ip_get = Ottenendo il vostro IP esterno e IP interno ip_warning = Il tuo IP esterno e' {{EXTERNAL_IP}}. Potrebbe essere necessario port-forward per il tuo IP interno {{INTERNAL_IP}} ip_confirm = Assicuratevi di controllarlo, se devi fare il port-forward e non lo fai, nessun giocatore esterno sara' in grado di entrare. [Premere Invio] you_have_finished = e' terminata correttamente la procedura guidata di installazione pocketmine_will_start = GenisysPro ora si avviera'. Digita /help per visualizzare l'elenco dei comandi disponibili. pocketmine_plugins = Controllare il Repository Plugin per aggiungere nuove funzionalita', minigiochi, o protezione avanzata per il vostro server language_has_been_selected = 日本語に設定されました skip_installer = セットアップウィザードをスキップしますか? welcome_to_pocketmine = GenisysProをインストールして頂きありがとうございます!サーバのセットアップを開始するにはライセンスに同意する必要があります。GenisysProはGPLライセンスに基づいて認可されており、これについてはこのフォルダ内のLICENSEファイルから確認することができます。 accept_license = ライセンスに同意しますか? you_have_to_accept_the_license = GenisysProを使用するにはGPLライセンスに同意する必要があります setting_up_server_now = サーバのセットアップを開始します default_values_info = 設定を変更しない場合は、Enterキーを押してください。 server_properties = それらの設定は後からでもserver.propertiesファイルから変更できます name_your_server = あなたのサーバに名前を付けてください port_warning = これが初めてのサーバの場合は、ポート番号をデフォルトから変更しないでください server_port = サーバポート invalid_port = 無効なサーバポートです online_mode_info = オンラインモードでは、ユーザーがXBOXを経由してログインすることを強制します。\nサーバーが外部に開放されている場合はオンラインモードを有効にすることをおすすめします。 online_mode = オンラインモードを有効にしますか? ram_warning = RAMはGenisysProが使用し得るメモリの最大値を示しています。128-256MBの範囲内で指定することを推奨します。 server_ram = RAMの単位はMBです gamemode_info = クリエイティブモード(1)、またはサバイバルモード(0)を選択してください default_gamemode = デフォルトのゲームモード max_players = 最大プレイヤー数 spawn_protection_info = スポーンプロテクションは、OPでないプレイヤーによるスポーン地点付近でのブロックの設置/破壊を制限します spawn_protection = スポーンプロテクションを有効にしますか? level_name = ワールド名を指定してください level_type = サーバーのワールドタイプを指定してください invalid_level_type = 無効なレベルタイプです announce_player_achievements = プレイヤーが実績を獲得したときにアナウンスしますか? op_info = OPとはそのサーバの管理権限を指します。OPを持ったプレイヤーは他のプレイヤーよりも多くのコマンドを使用できます。 op_who = OPプレイヤー名(例: あなたのゲーム内での名前) op_warning = 後から/op <プレイヤー名>コマンドを実行してOPプレイヤーを追加することもできます whitelist_info = ホワイトリストはこのサーバに入ることのできるプレイヤーを制限します。 whitelist_enable = ホワイトリストを有効にしますか? whitelist_warning = プレイヤーをホワイトリストに追加してください query_warning1 = クエリは他のツールによりあなたのサーバやプレイヤーの情報を取得するためのプロトコルです。 query_warning2 = それを無効にした場合、サーバリストを使用できなくなる可能性があります。 query_disable = クエリを無効にしますか? rcon_info = RCONはパスワードを用いてサーバコンソールからリモート接続するためのプロトコルです。 rcon_enable = RCONを有効にしますか? rcon_password = RCONパスワード(後から変更できます) usage_info = 世界中で使われているPocketMine-MPやそのプラグインの統計を算出するために匿名の使用データが送信されます。統計はこちらから確認できます。stats.pocketmine.net usage_disable = 匿名の使用データの送信を拒否しますか? ip_get = グローバルIPとプライベートIPの取得 ip_warning = あなたのグローバルIPは{{EXTERNAL_IP}}です。プライベートIP{{INTERNAL_IP}}をポート解放してください。 ip_confirm = 必ずポート解放ができているか確認してください。ポートが解放できていなかった場合、他のプレイヤーがサーバに入れなくなる恐れがあります。[Enterキーを押してください] you_have_finished = セットアップは正しく終了しました pocketmine_will_start = GenisysProを起動します。/helpと入力すれば使用可能なコマンド一覧を表示できます。 pocketmine_plugins = 拡張機能や管理システム、ミニゲームなどを追加できるプラグインリポジトリも確認してみてください language_has_been_selected = 한국어가 언어로 선택되었습니다. skip_installer = 설치 마법사를 건너뛰겠습니까? welcome_to_pocketmine = GenisysPro에 오신 것을 환영합니다!\n서버 설치를 시작하기 전, 약관에 동의해야 합니다. \nGenisysPro는 GNU 일반 공중 사용 허가서 (GPL) 하에 배포되고 있습니다. \n이 폴더에서 약관을 읽을 수 있습니다. accept_license = 약관에 동의하십니까? you_have_to_accept_the_license = GNU 약소 일반 공중 사용 허가서 (GPL)에 동의하셔야 GenisysPro를 사용할 수 있습니다. setting_up_server_now = 서버 설정을 시작합니다. default_values_info = 기본값을 수정하고 싶지 않으면, 엔터를 누르시기 바랍니다. server_properties = 이 설정들은 server.properties 파일에서 세부적으로 변경이 가능합니다. name_your_server = 당신의 서버 이름을 입력하시기 바랍니다. port_warning = 만약 당신이 서버를 처음 설정한다면 포트 값을 변경하지 마세요. server_port = 서버 포트 invalid_port = 서버 포트가 잘못 입력되었습니다. online_mode_info = 온라인 모드에서는 서버가 사용자를 XBOX를 사용해서만 로그인 하도록 합니다.\n외부 서버를 여는 경우 온라인 모드를 활성화 하는 것이 좋습니다. online_mode = 온라인 모드를 활성화 하시겠습니까? ram_warning = RAM 값은 GenisysPro에 할당할 메모리의 크기입니다. 128~256MB를 권장합니다. server_ram = 서버의 램(RAM) (MB) gamemode_info = 크리에이티브 (1) 또는 서바이벌 (0) 게임모드 중 하나를 고르세요. default_gamemode = 기본 게임 모드 max_players = 최대 동시 접속 인원 수 spawn_protection_info = 스폰 보호는 OP를 제외한 유저들이 스폰 지역 근처에서 블럭을 놓거나 부수는 것을 방지합니다. spawn_protection = 스폰 지역 보호를 사용하겠습니까? level_name = 레벨의 이름을 설정하시기 바랍니다. level_type = 레벨의 유형을 설정하시기 바랍니다. invalid_level_type = 레벨 유형이 잘못 입력되었습니다. announce_player_achievements = 플레이어가 도전 과제를 달성했을때 알리시겠습니까? op_info = OP는 서버 관리자를 뜻합니다. OP는 일반 플레이어보다 훨씬 많은 명령어들을 사용할 수 있습니다 op_who = OP 권한을 줄 플레이어 이름 (예: 당신의 닉네임) op_warning = 또는 이후에 /op <플레이어 이름>을 입력해 그 유저에게 OP 권한을 줄 수도 있습니다 whitelist_info = 화이트리스트를 사용하면 허용된 플레이어들만 서버에 접속할 수 있습니다. whitelist_enable = 화이트리스트를 사용하겠습니까? whitelist_warning = 당신은 접속을 허용할 플레이어들의 이름을 적어야 합니다 query_warning1 = 쿼리 (Query)는 당신의 서버와 현재 접속한 플레이어들의 정보를 알 수 있게 해주는 여러 가지 기능이 담긴 프로토콜입니다. query_warning2 = 쿼리를 사용하지 않으면, 당신은 서버 리스트를 사용할 수 없게 됩니다. query_disable = 쿼리를 사용하지 않겠습니까? rcon_info = RCON은 비밀 번호를 사용하여 서버 명령창에 원격으로 접속할 수 있는 프로토콜입니다. rcon_enable = RCON을 사용하시겠습니까? rcon_password = RCON 비밀번호 설정 (나중에 server.properties에서 변경이 가능합니다.) usage_info = 익명 사용 통계 전송을 허용하면 PocketMine-MP이 세계의 서버 상황과 플러그인들을 통계화하는 데 사용할 수 있습니다. 당신은 stats.pocketmine.net에서 이를 확인할 수 있습니다 usage_disable = 익명 사용 통계 전송을 사용하지 않으시겠습니까? ip_get = 내/외부 IP 주소 얻기 ip_warning = 당신의 외부 IP는 {{EXTERNAL_IP}}입니다. 당신의 내부 IP {{INTERNAL_IP}}(으)로 포트포워딩이 필요할 수 있습니다 ip_confirm = 신중하게 확인하세요. 만약 포트포워딩이 필요하지만 하지 않을 경우 외부에서 플레이어들이 접속할 수 없게 됩니다. [계속 하시려면 엔터를 누르세요] you_have_finished = GenisysPro의 설치가 모두 완료되었습니다. pocketmine_will_start = GenisysPro 서버를 구동합니다. /help로 사용 가능한 모든 명령어의 목록을 보시기 바랍니다. pocketmine_plugins = 플러그인 저장소에서 새로운 기능을 추가하세요. 새로운 기능, 미니게임을 추가하거나 고급 기능으로 당신의 서버를 보호할 수 있습니다 language_has_been_selected = Был выбран русский язык. skip_installer = Вы хотите пропустить мастер настройки? welcome_to_pocketmine = Добро пожаловать в GenisysPro!\nПеред началом установки нового сервера, вы должны согласиться с лицензией. \nGenisysPro лицензировано на условиях GPL лицензии, \nс которой вы можете ознакомиться, открыв файл LICENSE в этой папке. accept_license = Принимаете ли вы лицензию? you_have_to_accept_the_license = Вы должны принять GPL лицензию, чтобы продолжить использование GenisysPro setting_up_server_now = Теперь вы можете настроить сервер. default_values_info = Если вы не хотите изменять стандартное значение, просто нажмите Enter. server_properties = Вы можете редактировать их позже в файле server.properties. name_your_server = Дайте имя вашему серверу port_warning = Не изменяйте значение порта по умолчанию, если это ваш первый сервер. server_port = Порт сервера invalid_port = Неверный порт сервера online_mode_info = online-mode позволяет проверять игрока на аутентификацию в XBOX Live online_mode = Включить online-mode? ram_warning = RAM - максимальный объем памяти, котооый будет использовать GenisysPro, рекомендуемое значение 128-256 МБ server_ram = Оперативная память сервера в МБ gamemode_info = Выберите между Креативом-(1), или Выживанием (0) default_gamemode = Режим игры по умолчанию max_players = Максимум онлайн игроков spawn_protection_info = Защита спавна запрещает размещение/разрушение блоков на спавне, за исключением операторов spawn_protection = Включить защиту спавна? level_name = Дайте имя миру вашего сервера level_type = Выберите тип мира invalid_level_type = Неверный тип мира announce_player_achievements = Объявлять достижение игрока когда он его получил? op_info = Оператор - Администратор сервера, который может использовать больше команд, чем обычный игрок op_who = Имя оператора (К примеру ваш ник в игре) op_warning = Вы можете добавить оператора позже используя команду /op whitelist_info = White-list позволяет присоединиться только игрокам в этом списке. whitelist_enable = Вы хотите включить white-list? whitelist_warning = Вам придётся добавить игроков в white-list query_warning1 = Query это протокол, используемый разными инструментами, чтобы получить информацию о вашем сервере и о зашедших игроках. query_warning2 = Если вы отключите его, вы не сможете использовать списки серверов. query_disable = Вы хотите отключить Query? rcon_info = RCON - это протокол для удаленного управления сервером через консоль, с использованием пароля. rcon_enable = Вы хотите включить RCON? rcon_password = RCON пароль (вы сможете изменить его позже) usage_info = Сбор анонимных данных о использовании поможет нам проанализировать глобальную статистику для PocketMine-MP и плагинов. Статистика доступна по адресу stats.pocketmine.net usage_disable = Вы хотите отключить сбор анонимных данных о использовании? ip_get = Получение вашего внешнего и внутреннего IP адреса ip_warning = Ваш внешний IP адрес: {{EXTERNAL_IP}}. Возможно вам необходимо открыть порты к вашему внутреннему IP адресу: {{INTERNAL_IP}} ip_confirm = Убедитесь в том, что вы открыли порты, в противном случае игроки вне вашей локальной сети не смогут подключится. [Нажмите Enter] you_have_finished = Вы закончили работу с мастером установки pocketmine_will_start = GenisysPro сейчас запустится. Введите /help для просмотра доступных вам команд. pocketmine_plugins = Посетите репозиторий плагинов для получения новых функций, мини-игр, или повышения защиты вашего сервера language_has_been_selected = Було вибрано українську мову. skip_installer = Бажаєте пропустити майстер налаштування? welcome_to_pocketmine = Вітаємо в GenisysPro!\nПеред початком встановлення нового серверу, ви маєте погодитися з умовами ліцензії. \nGenisysPro ліцензовано на умовах GPL. \nДетально ви можете ознайомитися з нею у файлі LICENSE в цій теці. accept_license = Ви приймаєте умови ліцензії? you_have_to_accept_the_license = Ви маєте погодитися з умовами GPL ліцензії для подальшого користування GenisysPro setting_up_server_now = Тепер ви можете налаштувати сервер. default_values_info = Для вибору значень за замовчуванням просто натискайте Enter. server_properties = Пізніше ви зможете їх відредагувати у файлі server.properties. name_your_server = Вкажіть назву вашого серверу port_warning = Не змінюйте номер порту за замовчуванням, якщо ви налаштовуєте сервер вперше. server_port = Порт серверу invalid_port = Неправильний порт серверу online_mode_info = online-mode дозволяє перевіряти у гравця наявність аутентифікації в XBOX Live online_mode = Ввімкнути online-mode? ram_warning = RAM - максимальний об’єм пам’яті, який буде використовувати GenisysPro, рекомендоване значення - 128-256 МБ server_ram = Оперативна пам’ять серверу в МБ gamemode_info = Вкажіть 1 для Творчого режиму на сервері або 0 для Виживання default_gamemode = Режим гри за замовчуванням max_players = Максимальна кількість гравців spawn_protection_info = Захист місця появи гравців забороняє розміщення або руйнування блоків на місті появи гравця, за виключенням операторів spawn_protection = Ввімкнути захист спавну? level_name = Вкажіть назву світу level_type = Вкажіть тип світу invalid_level_type = Неправильний тип світу announce_player_achievements = Демонструвати досягнення гравців? op_info = Оператор - це адміністратор серверу, що може використовувати більше команд, ніж звичайний гравець op_who = Нік оператора (Наприклад, ваш нік в грі) op_warning = Ви зможете додати оператора пізніше за допомогою команди /op <гравець> whitelist_info = Білий список дозволяє заходити тільки тим гравцям, що знаходяться в ньому. whitelist_enable = Бажаєте ввімкнути білий список? whitelist_warning = Необхідно додати гравців до білого списку query_warning1 = QUERY - це протокол, що використовується різними інструментами для отримання інформації про сервер та про гравців серверу. query_warning2 = Якщо ви його вимкнете, то не зможете користуватися моніторингами серверів. query_disable = Ви хочете вимкнути QUERY? rcon_info = RCON - це протокол, який реалізує віддалене керування сервером. Захищене паролем. rcon_enable = Бажаєте ввімкнути RCON? rcon_password = RCON пароль (ви зможете змінити його пізніше) usage_info = Збір анонімних даних про користування допоможе нам проаналізувати глобальну статистику для PocketMine-MP и плагінів. Статистика доступна за адресою stats.pocketmine.net usage_disable = Бажаєте вимкнути збір анонімних даних про користування? ip_get = Отримання вашого внутрішньої і зовнішньої IP адрес ip_warning = Ваша зовнішня IP адреса: {{EXTERNAL_IP}}. Можливо, вам необхідно відкрити порти до вашої внутрішньої адреси: {{INTERNAL_IP}} ip_confirm = Переконайтеся в тому, що ви відкрили порти, інакше гравці поза локальною мережею не зможуть приєднатися. [Натисніть Enter] you_have_finished = Ви завершили роботу з майстром встановлення pocketmine_will_start = GenisysPro зараз запуститься. Введіть /help для перегляду доступних команд. pocketmine_plugins = Відвідайте сховище плагінів для отримання нових функцій, міні ігр або для підвищення захисту вашого серверу language_has_been_selected = 您選擇了繁體中文 skip_installer = 您想跳過安裝流程嗎? welcome_to_pocketmine = 歡迎來到GenisysPro的安裝流程!\n在開始設定您的伺服器之前您需要接受以下協議\nGenisysPro使用了LGPL協議\n你可以在資料夾中找到LICENSE文件 accept_license = 您接受協議內容嗎? you_have_to_accept_the_license = 您必須要接受LGPL協議才可以運行GenisysPro!! setting_up_server_now = 現在要開始設定您的伺服器了 default_values_info = 如果您希望使用預設值請直接按下Enter鍵,進行略過 server_properties = 別擔心設定錯誤,您以後還是可以在server.properties中修改設定 name_your_server = 為您的伺服器命名: port_warning = 如果這是您第一次設定伺服器,強烈建議不要變更接口 server_port = 伺服器接口: invalid_port = 不正確的伺服器接口 online_mode_info = 多人連線模式下, 伺服器將強制玩家需要 Xbox 登入\n當多人模式時, 建議讓伺服器開啟此功能 online_mode = 您想啟用多人模式? ram_warning = RAM是GenisysPro可用的最大記憶體。推薦範圍:128~256 MB server_ram = 分配給服務器的記憶體(MB): gamemode_info = 選擇模式:(0) 生存模式 或 (1) 創造模式 default_gamemode = 預設遊戲模式 max_players = 最大在線人數 spawn_protection_info = 出生點保護可以在出生點範圍內保護所有方塊不被放置與破壞 spawn_protection = 啟用出生點保護? level_name = 命名您的伺服器世界: level_type = 設置您的伺服器世界類型: invalid_level_type = 無效的世界類型 announce_player_achievements = 當玩家獲得成就時是否公告? op_info = OP是一個伺服器的管理員, 可以執行比普通玩家更多的指令 op_who = 請輸入管理員的玩家名稱 op_warning = 你可以執行"/op <玩家名稱>"來新增管理員 whitelist_info = 白名單可以只允許在其列表中的玩家加入 whitelist_enable = 您想啟用白名單嗎? whitelist_warning = 你可以用"/whitelist add <玩家名稱>"把玩家加入白名單 query_warning1 = Query請求是一個用於不同程式的協議用來獲取您伺服器數據和登入的玩家 query_warning2 = 如果您停用了它,您將不能使用伺服器列表 query_disable = 您希望停用Query請求嗎? rcon_info = RCON是一個用來遠程連接到伺服器控制台的協議(需要密碼) rcon_enable = 您希望啟用RCON嗎? rcon_password = RCON密碼 (您以後能更改它): usage_info = 匿名數據讓我們可以獲得全球的PocketMine-MP和它的插件的統計訊息,您可以在 stats.pocketmine.net 查看統計訊息 usage_disable = 您希望停用匿名數據嗎? ip_get = 取得你的外網IP和內網IP ip_warning = 您的外網IP是 {{EXTERNAL_IP}},您可能需要接口轉發到您的內網IP {{INTERNAL_IP}} ip_confirm = 請確認您檢查了它,如果您直接進入下一步並跳過這一步,沒有外部的玩家可以加入 [按\"Enter\"鍵] you_have_finished = 您已經成功完成了伺服器設定精靈 pocketmine_will_start = GenisysPro即將開始運行,輸入 "help" 可以查看所有可用的指令 pocketmine_plugins = 請查看插件來源以便新增新的功能,迷你遊戲或者對伺服器的高級保護 language.name = Čeština language.selected = Vybráno {%0} ({%1}) jako základní jazyk multiplayer.player.joined = {%0} se připojil do hry multiplayer.player.left = {%0} se odpojil ze hry chat.type.achievement = {%0} právě získal ocenění {%1} disconnectionScreen.outdatedClient = Zastaralá verze! disconnectionScreen.outdatedServer = Zastaralý server! disconnectionScreen.serverFull = Server je plný! disconnectionScreen.noReason = Odpojen ze serveru disconnectionScreen.invalidSkin = Neplatný skin! disconnectionScreen.invalidName = Nevhodný herní nick! death.fell.accident.generic = {%0} spadl z vysokého místa death.attack.inFire = {%0} shořel v ohni death.attack.onFire = {%0} uhořel k smrti death.attack.lava = {%0} se pokusil plavat v lávě death.attack.inWall = {%0} se udusil ve zdi death.attack.drown = {%0} se utopil death.attack.cactus = {%0} byl upíchán k smrti death.attack.generic = {%0} zemřel death.attack.explosion = {%0} vybouchnul death.attack.explosion.player = {%0} byl odbouchnut hráčem {%1} death.attack.magic = {%0} byl zabit pomocí magie death.attack.wither = {%0} zemřel na útok Withera death.attack.mob = {%0} byl zabit {%1} death.attack.player = {%0} byl zabit {%1} death.attack.player.item = {%0} byl zabit hráčem {%1} používajícím {%2} death.attack.arrow = {%0} byl zastřelen {%1} death.attack.arrow.item = {%0} byl zastřelen hráčem {%1} používajícím {%2} death.attack.fall = {%0} spadl z moc velké výšky death.attack.outOfWorld = {%0} vypadl ze světa gameMode.survival = §l§f"Mód Survival" gameMode.creative = §l§f"Mód Creative" gameMode.adventure = §l§f"Mód Adventure" gameMode.spectator = §l§f"Mód Spetractor" gameMode.changed = Tvůj herní mód byl úspěšně změněn na {%0} potion.moveSpeed = Rychlost potion.moveSlowdown = zpomalení potion.digSpeed = Rychle kopání potion.digSlowDown = Pomalé kopání potion.damageBoost = Síla potion.heal = Rychlé uzdravení potion.harm = Rychlé zranění potion.jump = vysoké skoky potion.confusion = nevolnost potion.regeneration = Regenerace potion.resistance = Odpor potion.fireResistance = Ohnivzdornost potion.waterBreathing = Dýchání pod vodou potion.invisibility = Neviditelnost potion.blindness = Hloupost potion.nightVision = Noční vidění potion.hunger = Hlad potion.weakness = Slabost potion.poison = Jed potion.wither = Wither potion.healthBoost = Zvýšení života potion.absorption = Absorbace potion.saturation = Saturace commands.generic.exception = Při pokusu o provedení tohoto příkazu došlo k neznámé chybě commands.generic.permission = §l§cNemas opravneni pouzit tento prikaz commands.generic.notFound = §l§cNeznamy příkaz. Pouzijte /help pro ziskani seznamu prikazu commands.generic.player.notFound = Tento hráč nemůže být nalezen commands.generic.usage = Vyuziti:{%0} commands.time.added = Přidáno {%0} do časů commands.time.set = Čas nastaven na {%0} commands.time.query = Čas je {%0} commands.me.usage = /me commands.give.item.notFound = Neexistuje žádný item s tímto ménem {%0} commands.give.success = Given {%0} * {%1} to {%2} commands.give.tagError = Rozbor data tagu neůspěšný: {%0} commands.effect.usage = /effect [seconds] [amplifier] [hideParticles] NEBO /effect clear commands.effect.notFound = Efekt s ID {%0} neexistuje commands.effect.success = Daný {%0} (ID {%1}) * {%2} na {%3} pro {%4} sekund commands.effect.success.removed = Vzal {%0} z {%1} commands.effect.success.removed.all = Vzal všechny efekty od {%0} commands.effect.failure.notActive = Nemůžu vzít {%0} od {%1} protože efekt nemá commands.effect.failure.notActive.all = Nemůžu získat efekt od {%0} protože žádný nemá commands.enchant.noItem = Terc nedrzi item commands.enchant.notFound = Tady není zadný enchant s ID {%0} commands.enchant.success = Enchant byl uspěšný commands.enchant.usage = /enchant [level] commands.particle.success = Zapínám efekt {%0} {%1} commands.particle.notFound = Neznámé méno efektu {%0} commands.players.usage = /seznam commands.players.list = {%0}/{%1} Hráčů online: commands.kill.successful = Zabit {%0} commands.banlist.ips = Celkem zabanovaných IP adres: %d commands.banlist.players = Je zabanováno {%0} Hráčů: commands.banlist.usage = /banlist [ips|players] commands.defaultgamemode.usage = /defaultgamemode commands.defaultgamemode.success = Základní herní mód je teď {%0} commands.op.success = Povýšen {%0} commands.op.usage = /op commands.deop.success = §l§cDe-opnut {%0} commands.deop.usage = /deop commands.say.usage = /say commands.seed.usage = /seed commands.seed.success = Seed:{%0} commands.ban.success = Zabanován hráč {%0} commands.ban.usage = /ban [Důvod ...] commands.unban.success = Odbanován hráč {%0} commands.unban.usage = /pardon commands.banip.invalid = Zadal jsi špatnou IP adresu nebo tento hráč není online commands.banip.success = Zabanována IP adresa {%0} commands.banip.success.players = Zabanovaná IP adresa {%0} byla přepsána na{%1} commands.banip.usage = /ban-ip [Důvod...] commands.unbanip.invalid = Zadal jsi špatnou ip adresu commands.unbanip.success = Odbanovali jste adresu ip {%0} commands.unbanip.usage = /pardon-ip
commands.save.usage = -ulozit-vse commands.save-on.usage = /zapnout-ukladani commands.save-off.usage = /vypnout-ulozeni commands.save.enabled = Automatické ukládání světa zapnuto commands.save.disabled = Automatické ukládání světa vypnuto commands.save.start = Ukládám... commands.save.success = Svět uložen commands.stop.usage = /stop commands.stop.start = Zastavuji server commands.kick.success = {%0} byl odpojen ze hry commands.kick.success.reason = {%0} byl kicknut ze serveru z duvodu: {%1} commands.kick.usage = /odpojit [důvod...] commands.tp.success = {%0} se portnul k {%1} commands.tp.success.coordinates = {%0} Se portnul na souřadnice {%1}, {%2}, {%3} commands.tp.usage = /tp [cílový hráč] OR /tp [cílový hráč] [ ] commands.whitelist.list = Nacházejí se zde {%0} (z {%1} viděných) whitelist hráčů: commands.whitelist.enabled = Zapnut whitelist commands.whitelist.disabled = Vypnut whitelist commands.whitelist.reloaded = Whitelist znova načten commands.whitelist.add.success = {%0} byl přidán na whitelist commands.whitelist.add.usage = /whitelist přidat commands.whitelist.remove.success = {%0} byl odebrán z whitelistu commands.whitelist.remove.usage = /whitelist odebrat commands.whitelist.usage = /whitelist commands.gamemode.success.self = Změnit svůj herní mód na {%2} commands.gamemode.success.other = Změněn herní mód hráče {%0} na {%1} commands.gamemode.usage = /gamemode [player] commands.help.header = --- Ukazuji pomoc - strana {%0} z {%1} (/help ) --- commands.help.usage = §l/help [strana|jméno příkazu] commands.message.usage = /tell commands.message.sameTarget = Nemůžeš poslat soukromou zprávu sám sobě! commands.difficulty.usage = /difficulty commands.difficulty.success = Herní obtížnost nastavena na {%0} commands.spawnpoint.usage = /spawnpoint [player] [ ] commands.spawnpoint.success = {%0}ův spawnpoint byl nastaven na ({%1}, {%2}, {%3}) commands.setworldspawn.usage = /setworldspawn [] commands.setworldspawn.success = Spawn světa byl nastaven na souřadnice ({%0}, {%1}, {%2}) pocketmine.data.playerNotFound = Data hráče {%0} nenalezena, vytvářím nový profil pocketmine.data.playerCorrupted = Špatná data hráče {%0}, vytvářím nový profil pocketmine.data.playerOld = Pro hráče {%0} nalezena stará data, vylepšuji profil pocketmine.data.saveError = Nemůžu nastavit hráče {%0}; {%1} pocketmine.level.notFound = Svět {%0} nebyl nalezen pocketmine.level.loadError = Nemůžu načíst svět {%0}; {%1} pocketmine.level.generationError = Nemůžu vytvořit svět {%0}; {%1} pocketmine.level.tickError = Nemůžu najít svět {%0}; {%1} pocketmine.level.chunkUnloadError = Chyba při načítáni chunku {%0} pocketmine.level.backgroundGeneration = Spawn pro svět {%0} bude vytvořen v pozadí pocketmine.level.defaultError = Žádný základní level nebyl načten pocketmine.level.preparing = Zapíná se svět {%0} pocketmine.level.unloading = Vypníná se svět {%0} pocketmine.server.start = Startuji Minecraft: PE server na verzi {%0} pocketmine.server.networkError = [Network] Zastavil interface {%0} protože {%1} pocketmine.server.networkStart = Startuji server na {%0}:{%1} pocketmine.server.info = Tento server běží na {%0} verzi {%1}"{%2}" (API {%3}) pocketmine.server.info.extended = Tento server používá {%0} {%1}「{%2}」implantována API {%3} pro Minecraft:PE {%4} (protokol {%5}) pocketmine.server.license = {%0} je distribuován pod LGP Licencí pocketmine.server.tickOverload = Pozor! Server je pretižen! pocketmine.server.startFinished = Hotovo ({%0}s)! Pro pomoc napiš "help" nebo "?" pocketmine.server.defaultGameMode = Základní herní mód je:{%0} pocketmine.server.query.start = Zapínám GS4 status poslouchač pocketmine.server.query.info = Nastavuji query port na {%0} pocketmine.server.query.running = Query běží na {%0}:{%1} pocketmine.command.alias.illegal = Nemůžu registrovat alias {%0} protože obsahuje nepoužitelné písmena pocketmine.command.alias.notFound = Nemůžu registrovat alias {%0} protože obsahuje příkaz který neexistuje:{%1} pocketmine.command.exception = Neošetřené použití příkazu '{%0}' in {%1}: {%2} pocketmine.command.plugins.description = Zobrazí list všech pluginů pocketmine.command.plugins.success = Pluginy ({%0}):{%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Přenačte kofiguraci serveru a jeho pluginy pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Znovu načítám data serveru.... pocketmine.command.reload.reloaded = Načtení serveru proběhlo úspěšně pocketmine.command.status.description = Vrátí zpátky výkon serveru. pocketmine.command.status.usage = /status pocketmine.command.gc.description = Fires garbage collection tasks pocketmine.command.gc.usage = /gc pocketmine.command.timings.description = Nahrává načasování aby viděl stav serveru. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Zapnuto časování & reset pocketmine.command.timings.disable = Vypnuto časování pocketmine.command.timings.timingsDisabled = Prosím zapněte nahrávání zadáním: /timings on pocketmine.command.timings.reset = Nastaven restart pocketmine.command.timings.pasteError = Při vkládání zprávy nastala chyba pocketmine.command.timings.timingsUpload = Nahrávání nahrána na {%0} pocketmine.command.timings.timingsRead = Můžete si přečíst výsledky na {%0} pocketmine.command.timings.timingsWrite = Nahrávání napsána do {%0} pocketmine.command.version.description = Dá vám informace o všech používaných pluginech pocketmine.command.version.usage = /version [plugin name] pocketmine.command.version.noSuchPlugin = Tento server nepoužívá žádný plugin s tímto jménem. Použij /plugins. pocketmine.command.give.description = Daruje specifikovanému hráči určený počet vybraných itemů pocketmine.command.give.usage = /give [amount] [tags...] pocketmine.command.kill.description = Zabití pocketmine.command.kill.usage = /kill [player] pocketmine.command.particle.description = Přidá prvek do světa pocketmine.command.particle.usage = /particle [count] [data] pocketmine.command.time.description = Nastaví čas sveta pocketmine.command.time.usage = /time NEBO /time pocketmine.command.ban.player.description = Zakáže hráči přístup na server pocketmine.command.ban.ip.description = Zakáže specifické IP adrese používat tento server pocketmine.command.banlist.description = Zobrazí všechny zabanované hráče pocketmine.command.defaultgamemode.description = Nastaví základní herní mód pocketmine.command.deop.description = Nastaví hráči op pocketmine.command.difficulty.description = Nastaví obtížnost hry pocketmine.command.enchant.description = Pridá enchant na itemy pocketmine.command.effect.description = Přidá/Odebere hráči op pocketmine.command.gamemode.description = Změní hráči herní mód pocketmine.command.help.description = Ukáže nápovědu pocketmine.command.kick.description = Odebere ban hráči pocketmine.command.list.description = Seznam všech připojených hráčů pocketmine.command.me.description = Udělá popsanou akci v chatu pocketmine.command.op.description = Dá danému hráči status operátora pocketmine.command.unban.player.description = Zruší ban danému hráči pocketmine.command.unban.ip.description = Zruší ban dané IP adrese pocketmine.command.save.description = Uloží data serveru na disk pocketmine.command.saveoff.description = Vypne automatické ukládání serveru pocketmine.command.saveon.description = Zapnuto automatické ukládání serveru pocketmine.command.say.description = Napíše zprávu jako odesílatel pocketmine.command.seed.description = Zobrazí seed světa pocketmine.command.setworldspawn.description = Nastaví spawn světa. Pokud nejsou použity žádné souřadnice, budou použity souřadnice hráče. pocketmine.command.spawnpoint.description = Nastaví spawn hráči pocketmine.command.stop.description = zastaví server pocketmine.command.tp.description = Teleportuje daného hráče (nebo vás) k jinému hráči nebo na jiné souřadnice pocketmine.command.tell.description = Pošle soukromou zprávu vybranému hráči pocketmine.command.whitelist.description = Spravuje list hráčů s povolením navštívit tento server pocketmine.crash.create = Neopravitelná chyba shodila server. Vytvářím report o chybe pocketmine.crash.error = Nemůžu vytvořit správu o chybě: {%0} pocketmine.crash.submit = Prosím nahrajte "{%0}" do archivu chyb a pošli link na stránku ohlašování chyb. Dej co nejvíc informací co můžeš. pocketmine.crash.archive = Zpráva o chybě byla nahrána do archivu chyb. Můžete se na ni podívat na {%0} nebo použít ID#{%1}. pocketmine.debug.enable = LevelDB podpora zapnuta pocketmine.player.invalidMove = {%0} se hýbe špatně! pocketmine.player.logIn = {%0}[/{%1}:{%2}] se připojil do hry s entitou id {%3} v ({%4},{%5},{%6},{%7}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] se odpojil kůli {%3} pocketmine.player.invalidEntity = {%0} umřel na útok neznámého moba pocketmine.plugin.load = Načítání {%0} pocketmine.plugin.enable = Povolení {%0} pocketmine.plugin.disable = Zakázání {%0} pocketmine.plugin.restrictedName = Omezené méno pocketmine.plugin.incompatibleAPI = Nekompatibilní API pocketmine.plugin.unknownDependency = Neznámá závislost v pluginu pocketmine.plugin.circularDependency = Byla nalezena oběžná závislost na pluginu pocketmine.plugin.genericLoadError = Nemůžu načíst plugin '{%0}' pocketmine.plugin.spacesDiscouraged = Plugin '{%0}' používá mezery ve svém jménu, to nejde pocketmine.plugin.loadError = Nemůžu načíst plugin '{%0}':{%1} pocketmine.plugin.duplicateError = Nemůžu načíst plugin '{%0}':plugin existuje pocketmine.plugin.fileError = Nemůžu načíst '{%0}' ve složce '{%1}':{%2} pocketmine.plugin.commandError = Nemůžu načíst příkaz {%0} pro plugin {%1} pocketmine.plugin.aliasError = Nemůžu načíst alias {%0} pro plugin {%1} pocketmine.plugin.deprecatedEvent = Plugin '{%0}' registroval Listener pro '{%1}' na metodě '{%2}', ale event není praktikován. pocketmine.plugin.eventError = "Nemůžu předělat event '{%0}' na'{%1}':{%2}na {%3}" # Language file compatible with Minecraft: Pocket Edition identifiers= #= # A message doesn't need to be there to be shown correctly on the client.= # Only messages shown in PocketMine itself need to be here= language.name = 中文(简体) language.selected = 设定 {%0} ({%1}) 为基本语言 multiplayer.player.joined = {%0} 加入了游戏 multiplayer.player.left = {%0} 离开了游戏 chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0} 刚刚获得了成就 {%1} disconnectionScreen.notAuthenticated = 需要 Xbox 登录 disconnectionScreen.outdatedClient = 客户端版本过旧! disconnectionScreen.outdatedServer = 服务器版本过旧! disconnectionScreen.serverFull = 服务器人数已满! disconnectionScreen.noReason = 与服务器联机中断 disconnectionScreen.invalidSkin = 无效的皮肤! disconnectionScreen.invalidName = 用户名不符合规范! disconnectionScreen.refusedResourcePack = 必须下载资源包才能加入游戏! disconnectionScreen.unavailableResourcePack = 无法在服务器上找到所需的资源包! death.fell.accident.generic = {%0} 发动了信仰之跃! death.attack.inFire = {%0} 在火焰中升天 death.attack.onFire = {%0} 被烧死了 death.attack.lava = {%0} 试图在岩浆里游泳 death.attack.inWall = {%0} 在墙内窒息而死 death.attack.drown = {%0} 淹死了 death.attack.cactus = {%0} 被仙人掌刺死了 death.attack.generic = {%0} 无疾而终 death.attack.explosion = {%0} 被炸飞了 death.attack.explosion.player = {%0} 被 {%1} 炸死了 death.attack.magic = {%0} 被魔法杀死了 death.attack.wither = {%0} 凋零至死了 death.attack.mob = {%0} 被 {%1} 杀死了 death.attack.player = {%0} 被 {%1} 杀死了 death.attack.player.item = {%0} 被 {%1} 用 {%2} 杀死了 death.attack.arrow = {%0} 被 {%1} 射杀了 death.attack.arrow.item = {%0} 被 {%1} 用 {%2} 射杀了 death.attack.fall = {%0} 发动了信仰之跃! death.attack.outOfWorld = {%0} 掉出了这个世界 gameMode.survival = 生存模式 gameMode.creative = 创造模式 gameMode.adventure = 冒险模式 gameMode.spectator = 旁观者模式 gameMode.changed = 您的游戏模式已更新 potion.moveSpeed = 速度 potion.moveSlowdown = 缓慢 potion.digSpeed = 急迫 potion.digSlowDown = 挖掘疲劳 potion.damageBoost = 力量 potion.heal = 瞬间治疗 potion.harm = 瞬间伤害 potion.jump = 跳跃提升 potion.confusion = 反胃 potion.regeneration = 生命恢复 potion.resistance = 抗性提升 potion.fireResistance = 防火 potion.waterBreathing = 水下呼吸 potion.invisibility = 隐身 potion.blindness = 失明 potion.nightVision = 夜视 potion.hunger = 饥饿 potion.weakness = 虚弱 potion.poison = 中毒 potion.wither = 凋零 potion.healthBoost = 生命提升 potion.absorption = 伤害吸收 potion.saturation = 饱和 commands.generic.exception = 出现问题,请通知管理员修复。 commands.generic.permission = 您没有权限使用此指令 commands.generic.notFound = 未知的指令。请尝试用 /help 来显示指令列表。 commands.generic.player.notFound = 找不到该玩家 commands.generic.usage = 用法:{%0} commands.generic.level = 地图名 commands.generic.seed = 种子 commands.generic.name = 名称 commands.generic.generator = 生成器名称 commands.generic.opt.missing = 指令缺少参数,请确认后重新输入。 commands.generic.runingame = 请在游戏中使用该命令。 commands.time.added = 时间增加了 {%0} commands.time.set = 时间设定为 {%0} commands.time.query = 现在时间是 {%0} commands.me.usage = /me commands.give.item.notFound = ID为 {%0} 的物品并不存在 commands.give.success = 将 {%0} * {%1} 给 {%2} commands.give.tagError = 数据格式不正确: {%0} commands.effect.usage = /effect <玩家名称> <效果> [秒数] [倍数] [隐藏粒子] 或 /effect <玩家名称> clear commands.effect.notFound = 然而ID为 {%0} 的特殊效果并不存在 commands.effect.success = 对 {%3} 加上了 {%4} 秒的 {%0} (ID {%1}) * {%2} commands.effect.success.removed = 从 {%1} 身上移除了 {%0} commands.effect.success.removed.all = 已解除 {%0} 身上所有特殊状态 commands.effect.failure.notActive = 无法从 {%1} 身上移除 {%0},因为其身上无此效果 commands.effect.failure.notActive.all = 无法移除效果,因为 {%0} 身上没有任何效果 commands.enchant.maxLevel = 该附魔的级别范围是 1 - {%0} commands.enchant.noItem = 目标没有手持一样物品 commands.enchant.notFound = 没有一个附魔ID为 {%0} commands.enchant.success = 附魔完成 commands.enchant.cantEnchant = 这件物品不能被附魔! commands.enchant.usage = /enchant <玩家名称> <附魔ID> [物品等级] commands.particle.success = 正在应用 {%0} 效果 {%1} 次 commands.particle.notFound = 未知的效果名称 {%0} commands.players.usage = /list commands.players.list = There are {%0}/{%1} players online: commands.kill.successful = 已删除 {%0} commands.banlist.ips = 共有 {%0} 个被封锁的 IP 地址: commands.banlist.players = 共有 {%0} 个被封锁的玩家: commands.banlist.cids = 共有 {%0} 禁止的 CIDs: commands.banlist.usage = /banlist [ips|players|cids] commands.defaultgamemode.usage = /defaultgamemode <模式> commands.defaultgamemode.success = 服务器的默认游戏模式为 {%0} commands.op.success = {%0} 获得管理员 pocketmine.command.op.usage = /op <玩家名称> commands.deop.success = {%0} 移除管理员 pocketmine.command.deop.usage = /deop <玩家名称> commands.say.usage = /say <讯息...> commands.seed.usage = /seed commands.seed.success = 种子码:{%0} commands.bancidbyname.success = 封锁玩家 {%0} 的CID commands.bancidbyname.usage = /bancidbyname commands.bancid.success = 封锁CID: {%0} commands.bancid.usage = /bancid commands.unbancid.usage = /pardoncid commands.ban.success = 封锁玩家 {%0} commands.ban.usage = /ban <玩家名称> [原因...] [时间(天)] commands.unban.success = 解锁玩家 {%0} commands.unban.usage = /pardon <玩家名称> commands.banip.invalid = 您输入了一个无效的 IP 地址、玩家名称或不在在线 commands.banip.success = 封锁 IP 地址 {%0} 。 commands.banip.success.players = 封锁 IP 地址 {%0} 来自 {%1} commands.banip.usage = /ban-ip [原因 ...] commands.unbanip.invalid = 您输入了一个无效的 IP 地址 commands.unbanip.success = 解除封锁 IP 地址 {%0} commands.unbanip.usage = /pardon-ip commands.banipbyname.success = 封锁玩家 {%0} 的IP commands.banipbyname.usage = /banipbyname commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = 开启地图自动存储功能 commands.save.disabled = 关闭地图自动存储功能 commands.save.start = 正在储存... commands.save.success = 世界储存完毕。 command.setblock.usage = /setblock <方块名称> [方块损害值] command.setblock.invalidBlock = 无效的方块名称/ID commands.stop.usage = /stop commands.stop.start = 服务器停止中 commands.kick.success = {%0} 从游戏中被踢出 commands.kick.success.reason = {%0} 从游戏中被踢出:{%1} commands.kick.usage = /kick <玩家名称> [原因...] commands.tp.success = 传送 {%0} 至 {%1} commands.tp.success.coordinates = 传送 {%0} 至 {%1},{%2},{%3} commands.tp.usage = /tp [玩家名称] <目标玩家> 或是 /tp [玩家名称] [ ] commands.whitelist.list = 有 {%0} 人(全部 {%1 人) 为白名单玩家: commands.whitelist.enabled = 开启白名单 commands.whitelist.disabled = 关闭白名单 commands.whitelist.reloaded = 重置白名单 commands.whitelist.add.success = 新增 {%0} 至白名单 commands.whitelist.add.usage = /whitelist add <玩家名称> commands.whitelist.remove.success = 从白名单删除 {%0} commands.whitelist.remove.usage = /whitelist remove <玩家名称> commands.whitelist.usage = /whitelist commands.gamemode.success.self = 设定自己的游戏模式为 {%2} commands.gamemode.success.other = 设定 {%0} 的游戏模式为 {%1} commands.gamemode.usage = /gamemode <模式> [玩家名称] commands.help.header = --- 查看帮助列表第 {%0} 页共 {%1} 页 (/help ) --- commands.help.usage = /help [页数|指令名称] commands.message.usage = /tell <玩家名称> <讯息...> commands.message.sameTarget = 您不能传送讯息给自己! commands.xp.usage = /xp <经验值或等级+L> <玩家名称> commands.difficulty.usage = /difficulty <难度> commands.difficulty.success = 设定游戏难度为 {%0} commands.spawnpoint.usage = /spawnpoint [玩家名称] [ ] commands.spawnpoint.success = 设定 {%0} 的重生点为 ({%1},{%2},{%3}) commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = 设定世界重生点为 ({%0},{%1},{%2}) commands.summon.usage = /summon [实体名] [ ] [数据标签] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = 无法找到玩家数据 "{%0}",创建新的配置文件 pocketmine.data.playerCorrupted = 发现损坏的数据 "{%0}",创建新的配置文件 pocketmine.data.playerOld = 发现旧的玩家数据 "{%0}",更新配置文件 pocketmine.data.saveError = 无法储存 "{%0}" 的玩家数据:{%1} pocketmine.level.notFound = 无法找到 "{%0}" 地图 pocketmine.level.loadError = 无法读取地图 "{%0}":{%1} pocketmine.level.generationError = 无法产生地图 "{%0}":{%1} pocketmine.level.tickError = 计算地图“{%0}”时出现错误∶{%1} pocketmine.level.chunkUnloadError = 移除一个区块时发生错误:{%0} pocketmine.level.backgroundGeneration = 正在于背景生成世界 “{%0}“ 的地形 pocketmine.level.defaultError = 没有读取预设的地图 pocketmine.level.preparing = 准备地图中... "{%0}" pocketmine.level.unloading = 正在移除地图 "{%0}" pocketmine.server.start = 正在启动支持 Minecraft:PE {%0} 版本的服务器 pocketmine.server.networkError = [网络] 停止接口 {%0} 由于 {%1} pocketmine.server.networkStart = 正在启动服务器在 {%0}:{%1} pocketmine.server.info = 此服务器正在运作 {%0} {%1} 版本 "{%2}" (API {%3}) pocketmine.server.info.extended = 此服务器正在运作 {%0} {%1} “{%2}” 执行 API 版本 {%3} 支持 Minecraft:PE {%4} (协议版本 {%5}) pocketmine.server.info.extended1 = 本服务器正运行 {%0}{%1} ({%2}) (代号 "{%3}") pocketmine.server.info.extended2 = PHP 版本: {%0} pocketmine.server.info.extended3 = API: {%0} pocketmine.server.info.extended4 = 客户端: Minecraft PE {%0} pocketmine.server.info.extended5 = 协议版本: {%0} pocketmine.server.license = {%0} 根据 LGPL 许可发布 pocketmine.server.tickOverload = 注意!服务器有超载的可能 pocketmine.server.startFinished = Done ({%0}s)!如需帮助,请输入 "help" 或 "?" pocketmine.server.defaultGameMode = 预设的游戏类型:{%0} pocketmine.server.query.start = 启动 GS4 状态监听器 pocketmine.server.query.info = 设定 query 接口到 {%0} pocketmine.server.query.running = Query 运作在 {%0}:{%1} pocketmine.command.alias.illegal = 不能注册别名 {%0},因为它包含非法字符 pocketmine.command.alias.notFound = 未能登记别称 {%0} ,因为它包含不存在的指令: {%1} pocketmine.command.exception = 于 {%1} 执行指令 “{%0}“ 时,出现了未被处理的错误: {%2} pocketmine.commands.cave.usage = /cave [<旋转角度> <洞穴长度> <分叉数> <洞穴强度> ] | /cave getmypos pocketmine.commands.cave.info = 旋转角度:{%0} 洞穴长度:{%1} 分叉数:{%2} 洞穴强度:{%3} pocketmine.commands.cave.start = 开始生成矿洞,可能需要较多时间 pocketmine.commands.cave.success = 矿洞生成完毕啦 pocketmine.command.plugins.description = 获取在服务器上运行的插件列表 pocketmine.command.plugins.success = 插件 ({%0}):{%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = 重新读取服务器设定和插件 pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = 重新读取服务器... pocketmine.command.reload.reloaded = 重新读取完成 pocketmine.command.lvdat.description = 修改地图属性 pocketmine.command.lvdat.changed = 对地图{%0}的{%1}属性修改已保存,部分属性可能需要重启服务器后生效。 pocketmine.command.lvdat.fixname = 已修复地图{%0}的名字,建议重启服务器。 pocketmine.command.lvdat.nofound = 地图{%0}没有创建或者加载失败。 pocketmine.command.lvdat.preset = 生成器选项(预设) pocketmine.command.status.description = 重新读取服务器的性能。 pocketmine.command.status.usage = /status pocketmine.command.status.title = 服务器状态 pocketmine.command.status.player = 服务器人数: pocketmine.command.status.days = 天 pocketmine.command.status.hours = 小时 pocketmine.command.status.minutes = 分 pocketmine.command.status.seconds = 秒 pocketmine.command.status.uptime = 运行时间: pocketmine.command.status.AverageTPS = 平均TPS: pocketmine.command.status.CurrentTPS = 瞬时TPS: pocketmine.command.status.Networkupload = 网络上传: pocketmine.command.status.Networkdownload = 网络下载: pocketmine.command.status.Threadcount = 线程总数: pocketmine.command.status.Mainmemory = 线程总数: pocketmine.command.status.Totalmemory = 总内存: pocketmine.command.status.Totalvirtualmemory = 总虚拟内存: pocketmine.command.status.Heapmemory = 堆栈内存: pocketmine.command.status.Maxmemorysystem = 系统最大内存: pocketmine.command.status.Maxmemorymanager = 核心全局最大内存: pocketmine.command.status.World = 世界 pocketmine.command.status.chunks = 区块, pocketmine.command.status.entities = 实体, pocketmine.command.status.tiles = tiles. pocketmine.command.status.Time = 时间 pocketmine.command.status.ms = 毫秒 pocketmine.command.gc.description = 回收垃圾 pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = 垃圾回收结果 pocketmine.command.gc.chunks = 区块: pocketmine.command.gc.entities = 实体: pocketmine.command.gc.tiles = 方块: pocketmine.command.gc.cycles = 循环: pocketmine.command.gc.memory = 内存释放: pocketmine.command.biome.description = 设置指定地图生物群系 pocketmine.command.biome.posset = 已设置第{%3}个坐标为:({%1},{%2})[{%0}] pocketmine.command.biome.get = 您所在的生物群系id为:{%0} 颜色值为:{%1},{%2},{%3} pocketmine.command.biome.wrongLev = 不能跨地图设置取点。 pocketmine.command.biome.wrongBio = 错误的生物群系ID,请输入数字ID(1=草原,2=沙漠,13=雪山,6=沼泽) pocketmine.command.biome.wrongCol = 错误的生物群系颜色,例如 146,188,89 可以使用 /biome get 获取 pocketmine.command.biome.noPos = 请先通过 /biome pos1|pos2 设定范围 pocketmine.command.biome.set = 已成功设置生物群系为:{%0} pocketmine.command.biome.color = 已成功设置生态颜色为:{%0},{%1},{%2} pocketmine.command.timings.description = 纪录计时数据,以检视服务器的性能。 pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = 启用定时和重启 pocketmine.command.timings.disable = 停用定时 pocketmine.command.timings.timingsDisabled = 启用定时工具透过 /timings on pocketmine.command.timings.reset = 定时重启 pocketmine.command.timings.pasteError = 已记录在事件记录文件中 pocketmine.command.timings.timingsUpload = 计时数据已被上载至 {%0} pocketmine.command.timings.timingsRead = 你可以在 {%0} 阅读计时结果 pocketmine.command.timings.timingsWrite = 计时数据已被储存至 {%0} pocketmine.command.version.description = 检视此服务器 (及其使用的插件) 的版本 pocketmine.command.version.usage = /version [插件名称] pocketmine.command.version.noSuchPlugin = 该服务器没有运行任何叫这个名称的插件。使用 /plugins 来获得插件列表。 pocketmine.command.give.description = 给指定玩家一定数量的物品 pocketmine.command.give.usage = /give <玩家名称> <项目[:损毁程度]> [数量] pocketmine.command.kill.description = 自杀或杀死其他玩家 pocketmine.command.kill.usage = /kill [玩家名称] pocketmine.command.particle.description = 加入粒子效果至世界 pocketmine.command.particle.usage = /particle <玩家名称> [数量] [数据值] pocketmine.command.time.description = 更改每个世界的时间 pocketmine.command.time.usage = /time <数值> 或 /time pocketmine.command.bancidbyname.description = 禁止指定玩家的设备 ID pocketmine.command.bancid.description = 禁止指定的设备 ID pocketmine.command.banipbyname.description = 禁止指定玩家的 IP pocketmine.command.ban.player.description = 禁止指定的玩家使用此服务器 pocketmine.command.ban.ip.description = 禁止指定的 IP 地址使用此服务器 pocketmine.command.banlist.description = 查看来自该服务器禁止的所有玩家 pocketmine.command.defaultgamemode.description = 设定默认的游戏模式 pocketmine.command.deop.description = 移除指定玩家的管理员权限 pocketmine.command.difficulty.description = 设定游戏的难易度 pocketmine.command.enchant.description = 把物件附魔 pocketmine.command.effect.description = 增加/减少玩家身上的效果 pocketmine.command.gamemode.description = 改变玩家到一个特定的游戏模式 pocketmine.command.help.description = 显示帮助列表 pocketmine.command.kick.description = 从服务器中删除指定玩家 pocketmine.command.list.description = 显示在线玩家列表 pocketmine.command.me.description = 于聊天中作出指定的动作 pocketmine.command.op.description = 赋予指定玩家管理员权限 pocketmine.command.unban.cid.description = 允许指定 CID 使用此服务器 pocketmine.command.unban.player.description = 允许指定玩家使用此服务器 pocketmine.command.unban.ip.description = 允许指定 IP 地址使用此服务器 pocketmine.command.save.description = 储存服务器到磁盘上 pocketmine.command.saveoff.description = 停用自动储存服务器 pocketmine.command.saveon.description = 启用自动储存服务器 pocketmine.command.say.description = 以发送指令者身份广播指定的讯息 pocketmine.command.seed.description = 显示世界种子码 pocketmine.command.setworldspawn.description = 设定一个世界重生点。未指定坐标,将使用玩家的坐标。 pocketmine.command.spawnpoint.description = 设定玩家重生点 pocketmine.command.stop.description = 关闭服务器 pocketmine.command.tp.description = 传送指定玩家(或是自己)到另一位玩家或坐标 pocketmine.command.tell.description = 传送私讯给指定玩家 pocketmine.command.xp.description = 给指定玩家增加经验值或等级 pocketmine.command.summon.description = 召唤指定的实体于玩家位置或指定位置 ocketmine.command.fill.description = 填充了指定方块 pocketmine.command.setblock.description = 将一个方块更改为另一个方块 pocketmine.command.weather.description = 设置指定地图的天气 pocketmine.command.weather.usage = /weather <世界名 天气值|天气值> pocketmine.command.weather.changed = 成功设置世界{%0}的天气! pocketmine.command.weather.noregistered = 世界{%0}没有注册到天气管理器! pocketmine.command.weather.invalid = 无效的天气!请输入天气类型 0,1,2,3 pocketmine.command.weather.wrong = 缺少参数! pocketmine.command.weather.invalid.level = 错误的地图名 pocketmine.command.whitelist.description = 管理员允许使用此服务器的玩家列表 pocketmine.crash.create = 一个未知的错误发生了,使服务器崩溃。正在储存错误报告。 pocketmine.crash.error = 未能储存错误报告∶{%0} pocketmine.crash.submit = 请上载档案“{%0}”至在线崩溃储存库。请尽量提供更多数据。 pocketmine.crash.archive = 错误报告已经上传到在线崩溃储存库。你可以在{%0} 查看到它或使用ID #{%1}。 pocketmine.debug.enable = 启用 LevelDB 支援 pocketmine.player.invalidMove = {%0} 行动可疑! pocketmine.player.logIn = {%0}[/{%1}:{%2}] [ClientID: {%3}] logged in with entity id {%4} at ({%5}, {%6}, {%7}, {%8}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] logged out due to {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}] 被传送到了 {%3} pocketmine.player.invalidEntity = {%0} 尝试攻击一个无效的实体 pocketmine.plugin.load = 读取中... {%0} pocketmine.plugin.enable = 开启中... {%0} pocketmine.plugin.disable = 关闭中... {%0} pocketmine.plugin.restrictedName = 受限的名称 pocketmine.plugin.incompatibleAPI = 不兼容的API版本 pocketmine.plugin.unknownDependency = 本插件无法单独使用 pocketmine.plugin.circularDependency = 检测出循环依赖 pocketmine.plugin.genericLoadError = 无法读取插件 '{%0}' pocketmine.plugin.spacesDiscouraged = 插件 '{%0}' 在名称中使用了空格,不建议这样做 pocketmine.plugin.loadError = 无法读取插件 '{%0}':{%1} pocketmine.plugin.duplicateError = 无法读取插件 '{%0}':已有相同插件 pocketmine.plugin.fileError = 无法读取在 '{%1}' 文件夹中的 '{%0}':{%2} pocketmine.plugin.commandError = 无法读取 {%1} 插件的 {%0} 指令 pocketmine.plugin.aliasError = 无法读取 {%1} 插件的 {%0} 别名 pocketmine.plugin.deprecatedEvent = 插件 '{%0}' 已经使用 '{%2}' 方法注册了一个在 '{%1}' 的监听器,但是该事件已过时。 pocketmine.plugin.eventError = "无法处理事件 '{%0}' 至 '{%1}':{%2} 在 {%3} 上" pocketmine.resourcepacks.createFolder = 资源包路径 {%0} 不存在,创建文件夹中... pocketmine.resourcepacks.notFolder = 资源包路径 {%0} 已经存在且不是一个文件夹 pocketmine.resourcepacks.load = 加载资源包... pocketmine.resourcepacks.folderNotSupported = 文件夹的资源包 {%0} 暂时不支持,请压缩 pocketmine.resourcepacks.unsupportedType = 未知类型资源包 {%0} 暂时未支持 pocketmine.resourcepacks.packNotFound = 找不到资源包 {%0} pocketmine.resourcepacks.loadFinished = 成功加载 {%0} 个资源包 # Language file compatible with Minecraft: Pocket Edition identifiers # # A message doesn't need to be there to be shown correctly on the client. # Only messages shown in PocketMine itself need to be here language.name = Deutsch language.selected = {%0} ({%1}) als Basissprache ausgewählt multiplayer.player.joined = {%0} hat das Spiel betreten multiplayer.player.left = {%0} hat das Spiel verlassen chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0} hat gerade den Erfolg {%1} erzielt disconnectionScreen.notAuthenticated = Xbox Anmeldung notwendig disconnectionScreen.outdatedClient = Veralteter Client! disconnectionScreen.outdatedServer = Veralteter Server! disconnectionScreen.serverFull = Server ist voll! disconnectionScreen.noReason = Verbindung zum Server getrennt disconnectionScreen.invalidSkin = Ungültiger Skin! disconnectionScreen.invalidName = Ungültiger Name! death.fell.accident.generic = {%0} fiel aus zu großer Höhe death.attack.inFire = {%0} ging in Flammen auf death.attack.onFire = {%0} verbrannte death.attack.lava = {%0} versuchte in Lava zu schwimmen death.attack.inWall = {%0} wurde lebendig begraben death.attack.drown = {%0} ertrank death.attack.cactus = {%0} wurde zu Tode gestochen death.attack.generic = {%0} starb death.attack.explosion = {%0} wurde in die Luft gesprengt death.attack.explosion.player = {%0} wurde durch {%1} in die Luft gesprengt death.attack.magic = {%0} wurde durch Magie getötet death.attack.wither = {%0} verdorrte death.attack.mob = {%0} wurde von {%1} erschlagen death.attack.player = {%0} wurde von {%1} erschlagen death.attack.player.item = {%0} wurde von {%1} mit {%2} erschlagen death.attack.arrow = {%0} wurde von {%1} erschossen death.attack.arrow.item = {%0} wurde von {%1} mit {%2} erschossen death.attack.fall = {%0} fiel der Schwerkraft zum Opfer death.attack.outOfWorld = {%0} fiel aus der Welt gameMode.survival = Überlebensmodus gameMode.creative = Kreativmodus gameMode.adventure = Abenteuermodus gameMode.spectator = Zuschauermodus gameMode.changed = Dein Spielmodus wurde geändert potion.moveSpeed = Schnelligkeit potion.moveSlowdown = Langsamkeit potion.digSpeed = Eile potion.digSlowDown = Langsames Abbauen potion.damageBoost = Stärke potion.heal = Direktheilung potion.harm = Direktschaden potion.jump = Sprungkraft potion.confusion = Übelkeit potion.regeneration = Regeneration potion.resistance = Resistenz potion.fireResistance = Feuerresistenz potion.waterBreathing = Unterwasser-Atmung potion.invisibility = Unsichtbarkeit potion.blindness = Blindheit potion.nightVision = Nachtsicht potion.hunger = Hunger potion.weakness = Schwäche potion.poison = Gift potion.wither = Dürre potion.healthBoost = Lebenserweiterung potion.absorption = Absorption potion.saturation = Sättigung commands.generic.exception = Ein unbekannter Fehler trat auf, während versucht wurde, diesen Befehl auszufüren commands.generic.permission = Du hast keine Berechtigung, um diesen Befehl auszuführen commands.generic.notFound = Unbekannter Befehl. Gib /help ein, um eine Liste von Befehlen zu erhalten commands.generic.player.notFound = Dieser Spieler kann nicht gefunden werden commands.generic.usage = Aufruf: {%0} commands.generic.level = Weltname commands.generic.seed = Startwert commands.generic.name = Name commands.generic.generator = Generator commands.generic.opt.missing = Notwendige Eigenschaft fehlt, bitte überprüfen und neu eingeben. commands.generic.runingame = Dieser Befehl kann nur im Spiel ausgeführt werden. commands.time.added = {%0} zur Zeit hinzugefügt commands.time.set = Setzte die Zeit auf {%0} commands.time.query = Zeit: {%0} commands.me.usage = /me commands.give.item.notFound = Es gibt kein Gegenstand mit dem Namen {%0} commands.give.success = {%0} * {%1} an {%2} gegeben commands.give.tagError = Parsen des Datatags fehlgeschlagen: {%0} commands.effect.usage = /effect [Sekunden] [Verstärker] [versteckePartikel] ODER /effect clear commands.effect.notFound = Es gibt keinen Mob-Effekt mit der ID {%0} commands.effect.success = {%0} (ID {%1}) * {%2} für {%4} Sekunden an {%3} gegeben commands.effect.success.removed = {%0} von {%1} genommen commands.effect.success.removed.all = Alle Effekte von {%0} entfernt commands.effect.failure.notActive = Konnte {%0} nicht von {%1} nehmen, da der Effekt nicht vorhanden war commands.effect.failure.notActive.all = Konnte keinen Effekt von {%0} nehmen, da kein Effekt vorhanden war commands.enchant.maxLevel = Das Maximale Verzauberungs Level der Verzauberung geht von 1 - {%0} commands.enchant.noItem = Dieser Spieler hält keinen Gegenstand in der Hand commands.enchant.notFound = Es gibt keine Verzauberung mit der ID {%0} commands.enchant.success = Verzauberung erfolgreich commands.enchant.cantEnchant = Die gewählte Verzauberung kann diesem Gegenstand nicht hinzugefügt werden. commands.enchant.usage = /enchant [Level] commands.particle.success = Erzeuge {%1}-fach den Partikeleffekt {%0} commands.particle.notFound = Unbekannter Partikeleffekt {%0} commands.players.usage = /list commands.players.list = Es sind {%0}/{%1} Spieler online: commands.kill.successful = {%0} wurde getötet commands.banlist.ips = Es sind insgesamt {%0} IP-Adressen gebannt: commands.banlist.players = Es sind insgesamt {%0} Spieler gebannt: commands.banlist.cids = Es sind insgesamt {%0} CIDs gebannt: commands.banlist.usage = /banlist [ips|players|cids] commands.defaultgamemode.usage = /defaultgamemode commands.defaultgamemode.success = Der Standart-Spielmodus der Welt ist jetzt {%0} commands.op.success = {%0} wurde zum Operator ernannt commands.op.usage = /op commands.deop.success = {%0} ist jetzt kein Operator mehr commands.deop.usage = /deop commands.say.usage = /say commands.seed.usage = /seed commands.seed.success = Startwert: {%0} commands.bancidbyname.success = CID des Spielers {%0} wurde gebannt commands.bancidbyname.usage = /bancidbyname commands.bancid.success = CID {%0} wurde gebannt commands.bancid.usage = /bancid commands.unbancid.usage = /pardoncid commands.unbancid.success = CID {%0} wurde entbannt commands.ban.success = Spieler {%0} wurde gebannt commands.ban.usage = /ban [Grund ...] [Zeit(Tag)] commands.unban.success = Spieler {%0} wurde entbannt commands.unban.usage = /pardon commands.banip.invalid = Du hast eine ungültige IP-Adresse oder einen Spieler, der nicht online ist angegeben commands.banip.success = IP-Adresse {%0} wurde gebannt commands.banip.success.players = IP-Adresse {%0}, die zu {%1} gehört, wurde gebannt commands.banip.usage = /ban-ip [Grund ...] commands.unbanip.invalid = Du hast eine ungültige IP-Adresse angegeben commands.unbanip.success = IP-Adresse {%0} wurde entbannt commands.unbanip.usage = /pardon-ip commands.banipbyname.success = IP-Adresse des Spielers {%0} wurde gebannt commands.banipbyname.usage = /banipbyname commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = Automatische Welt-Speicherung aktiviert commands.save.disabled = Automatische Welt-Speicherung deaktiviert commands.save.start = Speichern... commands.save.success = Welt wurde gespeichert commands.setblock.usage = /setblock [Schaden] command.setblock.invalidBlock = Blockname/ID ist ungültig commands.stop.usage = /stop commands.stop.start = Server wird beendet commands.kick.success = {%0} wurde aus dem Spiel geworfen commands.kick.success.reason = {%0} wurde aus dem Spiel geworfen: '{%1}' commands.kick.usage = /kick [Grund ...] commands.tp.success = {%0} wurde zu {%1} teleportiert commands.tp.success.coordinates = {%0} wurde zu {%1}, {%2}, {%3} teleportiert commands.tp.usage = /tp [Spieler] ODER /tp [Spieler] [ ] commands.whitelist.list = Es sind insgesamt {%0} (von {%1} hier gewesenen) Spieler in der Whitelist: commands.whitelist.enabled = Whitelist aktiviert commands.whitelist.disabled = Whitelist deaktiviert commands.whitelist.reloaded = Whitelist neu eingelesen commands.whitelist.add.success = {%0} zur Whitelist hinzugefügt commands.whitelist.add.usage = /whitelist add commands.whitelist.remove.success = {%0} von der Whitelist entfernt commands.whitelist.remove.usage = /whitelist remove commands.whitelist.usage = /whitelist commands.gamemode.success.self = Eigenen Spielmodus auf {%2} gesetzt commands.gamemode.success.other = {%0}'s Spielmodus auf {%1} gesetzt commands.gamemode.usage = /gamemode [Spieler] commands.help.header = --- Zeige Hilfe-Seite {%0} von {%1} (/help ) --- commands.help.usage = /help [Seite|Befehl] commands.message.usage = /tell commands.message.sameTarget = Du kannst keine private Nachricht an dich selbst schicken! commands.difficulty.usage = /difficulty commands.difficulty.success = Schwierigkeitsgrad auf {%0} gesetzt commands.spawnpoint.usage = /spawnpoint [Spieler] [ ] commands.spawnpoint.success = {%0}'s Spawnpunkt wurde auf ({%1}, {%2}, {%3}) gesetzt commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = Der Welt-Spawn wurde auf ({%0}, {%1}, {%2}) gesetzt commands.summon.usage = /summon [Entity] [ ] [NBTTag] commands.xp.failure.withdrawXp = Einem Spieler können keine negativen Erfahrungspunkte gegeben werden commands.xp.success = {%0} Erfahrungspunkte an Spieler {%1} gegeben commands.xp.success.levels = {%0} Level Erfahrung an {%1} gegeben commands.xp.success.negative.levels = {%0} Level Erfahrung von {%1} genommen commands.xp.usage = /xp [Spieler] ODER /xp L [Spieler] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = Keine Spielerdaten für "{%0}" gefunden, erstelle neues Profil pocketmine.data.playerCorrupted = Defekte Daten für Spieler "{%0}" gefunden, erstelle neues Profil pocketmine.data.playerOld = Alte Spielerdaten für "{%0}" gefunden, aktualisiere Profil pocketmine.data.saveError = Konnte Spieler "{%0}" nicht speichern: {%1} pocketmine.level.notFound = Welt "{%0}" nicht gefunden pocketmine.level.loadError = Welt "{%0}" konnte nicht geladen werden: {%1} pocketmine.level.generationError = Welt "{%0}" konnte nicht generiert werden: {%1} pocketmine.level.tickError = Welt "{%0}" konnte nicht getickt werden: {%1} pocketmine.level.chunkUnloadError = Fehler beim Entladen eines Chunks: {%0} pocketmine.level.backgroundGeneration = Spawn-Landschaft wird für die Welt "{%0}" im Hintergrund generiert pocketmine.level.defaultError = Es wurde keine Standard-Welt geladen pocketmine.level.preparing = Welt "{%0}" wird vorbereitet pocketmine.level.unloading = Welt "{%0}" wird entladen pocketmine.server.start = Starte Minecraft: PE server version {%0} pocketmine.server.networkError = [Netzwerk] Schnittstelle {%0} wurde wegen {%1} beendet pocketmine.server.networkStart = Öffne Server auf {%0}:{%1} pocketmine.server.info = Dieser Server wird mit {%0}{%1} "{%2}" (API {%3}) betrieben pocketmine.server.info.extended.title = -----Server Information----- pocketmine.server.info.extended1 = Dieser Server läuft mit Version {%0}{%1} ({%2}) (Codename "{%3}") pocketmine.server.info.extended2 = PHP Version: {%0} pocketmine.server.info.extended3 = API: {%0} pocketmine.server.info.extended4 = Zielclient: Minecraft PE {%0} (Protokollversion {%1}) pocketmine.server.license = {%0} wurde unter der GPL Lizenz Version 3 und neuer herausgegeben pocketmine.server.tickOverload = Server kann nicht mehr am Laufen gehalten werden! Ist der Server überladen? pocketmine.server.startFinished = Fertig ({%0}s)! Für Hilfe kannst du "help" oder "?" eingeben pocketmine.server.defaultGameMode = Standard-Spielmodus: {%0} pocketmine.server.query.start = Starte GS4 Status-Listener pocketmine.server.query.info = Setze Query-Port auf {%0} pocketmine.server.query.running = Query läuft auf {%0}:{%1} pocketmine.command.alias.illegal = Alias {%0} konnte nicht registriert werden, da er unzulässige Zeichen enthält pocketmine.command.alias.notFound = Alias {%0} konnte nicht registriert werden, da er Befehle enthält, die nicht existieren: {%1} pocketmine.command.exception = Unbehandelte Ausnahme bei Ausführung des Befehls '{%0}' in {%1}: {%2} pocketmine.commands.cave.usage = /cave | /cave getmypos pocketmine.commands.cave.info = Rotationswinkel:{%0} Länge:{%1} Abzweigungen:{%2} Stärke:{%3} pocketmine.commands.cave.start = Höhle wird generiert, bitte warten pocketmine.commands.cave.success = Höhle wurde generiert pocketmine.command.plugins.description = Zeigt eine Liste aller auf dem Server laufenden Plugins pocketmine.command.plugins.success = Plugins ({%0}): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Lädt die Serverkonfiguration und Plugins neu pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Server wird neu geladen... pocketmine.command.reload.reloaded = Neuladen des Servers abgeschlossen pocketmine.command.lvdat.description = Ändert die Eigenschaften einer Welt. pocketmine.command.lvdat.changed = {%1} der Welt "{%0}" wurde geändert, manche Änderungen erfordern einen Neustart deines Servers. pocketmine.command.lvdat.fixname = Fixname erfolgreich für die Welt "{%0}" abgeschlossen, manche Änderungen erfordern einen Neustart deines Servers. pocketmine.command.lvdat.nofound = Die Welt "{%0}" wurde nicht gefunden oder konnte nicht geladen werden. pocketmine.command.lvdat.preset = Generator Einstellungen (Vorlagen) pocketmine.command.status.description = Zeigt die aktuelle Serverauslastung an. pocketmine.command.status.usage = /status pocketmine.command.status.title = Server-Status pocketmine.command.status.player = Spieleranzahl: pocketmine.command.status.days = Tage pocketmine.command.status.hours = Stunden pocketmine.command.status.minutes = Minuten pocketmine.command.status.seconds = Sekunden pocketmine.command.status.uptime = Laufzeit: pocketmine.command.status.AverageTPS = Durchschnittliche TPS: pocketmine.command.status.CurrentTPS = Aktuelle TPS: pocketmine.command.status.Networkupload = Netzwerk Upload: pocketmine.command.status.Networkdownload = Netzwerk Download: pocketmine.command.status.Threadcount = Anzahl der Threads: pocketmine.command.status.Mainmemory = Speicher des Main-Threads: pocketmine.command.status.Totalmemory = Speicher gesamt: pocketmine.command.status.Totalvirtualmemory = Virtueller Speicher gesamt: pocketmine.command.status.Heapmemory = Heap-Speicher: pocketmine.command.status.Maxmemorysystem = Maximaler Speicher (System): pocketmine.command.status.Maxmemorymanager = Maximaler Speicher (Manager): pocketmine.command.status.World = Welt pocketmine.command.status.chunks = Chunks, pocketmine.command.status.entities = Entities, pocketmine.command.status.tiles = Tiles. pocketmine.command.status.Time = Zeit pocketmine.command.status.ms = ms pocketmine.command.gc.description = Startet die Tasks zur automatischen Speicherbereinigung (garbage collection) pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = Sammelbericht pocketmine.command.gc.chunks = Chunks: pocketmine.command.gc.entities = Entities: pocketmine.command.gc.tiles = Tiles: pocketmine.command.gc.cycles = Zyklen: pocketmine.command.gc.memory = Freigegebener Speicher: pocketmine.command.biome.description = Ändert die Biome der Region.(um zu Schnee oder Regen zu wechseln) pocketmine.command.biome.posset = Position {%3} bei ({%1},{%2}) der Welt {%0} festgelegt. pocketmine.command.biome.get = Die ID des Bioms, in dem du gerade bist ist {%0}. Farbe: {%1},{%2},{%3} pocketmine.command.biome.wrongLev = Die Position kann nicht in unterschiedlichen Welten festgelegt werden. pocketmine.command.biome.wrongBio = Falsche Biom-ID - Beispiele: 1 (Prärie), 2 (Wüste),13 (Eisberge),6 (Sumpfland) pocketmine.command.biome.wrongCol = Falsche Farbe - Beispiel: 146,188,89. Nutze "/biome get" um eine andere Farbe zu erhalten. pocketmine.command.biome.noPos = Bitte nutze "/biome Pos1|Pos2", um zuerst den Bereich festzulegen. pocketmine.command.biome.set = Das Biom des ausgewähltes Bereichs wurde auf {%0} gesetzt. pocketmine.command.biome.color = Die Grasfarbe wurde auf {%0},{%1},{%2} gesetzt. pocketmine.command.timings.description = Nimmt Zeitmessungen auf, um die Severauslastung zu ermitteln. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Timings & Reset aktiviert pocketmine.command.timings.disable = Timings deaktiviert pocketmine.command.timings.timingsDisabled = Bitte aktiviere die Timings, indem du '/timings on' eingibst pocketmine.command.timings.reset = Timings zurückgesetzt pocketmine.command.timings.pasteError = Während der Übernahme des Berichts trat ein Fehler auf pocketmine.command.timings.timingsUpload = Timings wurden hochgeladen zu {%0} pocketmine.command.timings.timingsRead = Du kannst dir die Ergebnisse hier anschauen {%0} pocketmine.command.timings.timingsWrite = Timings wurden gespeichert in {%0} pocketmine.command.version.description = Ermittelt die Version des Servers inklusive jedem verwendeten Plugin. pocketmine.command.version.usage = /version [Plugin-Name] pocketmine.command.version.noSuchPlugin = Auf diesem Server läuft kein Plugin mit diesem Namen. Mit /plugins kannst du dir eine Liste aller Plugins anzeigen lassen. pocketmine.command.give.description = Gibt dem angegebenen Spieler eine bestimmte Anzahl an Gegenständen (Items) pocketmine.command.give.usage = /give [Anzahl] [NBT-Daten...] pocketmine.command.kill.description = Tötet dich selbst oder andere Spieler pocketmine.command.kill.usage = /kill [Spieler] pocketmine.command.particle.description = Fügt einer Welt Partikel hinzu pocketmine.command.particle.usage = /particle [Anzahl] [Wert] pocketmine.command.time.description = Ändert in allen Welten die Zeit pocketmine.command.time.usage = /time ODER /time pocketmine.command.bancidbyname.description = Verhindet, dass sich die durch den Spielernamen ermittelte CID mit dem Server verbinden kann pocketmine.command.bancid.description = Verhindert, dass sich die angegebene CID mit dem Server verbinden kann pocketmine.command.banipbyname.description = Verhindet, dass sich die durch den Spielernamen ermittelte IP-Adresse mit dem Server verbinden kann pocketmine.command.ban.player.description = Verhindert, dass sich der angegebene Spieler mit dem Server verbinden kann pocketmine.command.ban.ip.description = Verhindert, dass sich die angegebene IP-Adresse mit dem Server verbinden kann pocketmine.command.banlist.description = Zeigt alle Spieler an, die von diesem Server gebannt wurden pocketmine.command.defaultgamemode.description = Legt den Standard-Spielmodus fest pocketmine.command.deop.description = Nimmt dem angegebenen Spieler den Operator-Status pocketmine.command.difficulty.description = Legt den Schwierigkeitsgrad des Spiels fest pocketmine.command.enchant.description = Fügt Gegenständen Verzauberungen hinzu pocketmine.command.effect.description = Fügt Spielern Effekte hinzu oder entfernt sie pocketmine.command.gamemode.description = Ändert den Spiemodus eines Spielers wie angegeben pocketmine.command.help.description = Zeigt das Hilfe-Menü an pocketmine.command.kick.description = Trennt den angegebenen Spieler vom Server pocketmine.command.list.description = Zeigt alle Spieler an, die gerade online sind pocketmine.command.me.description = Stellt die angegebene Aktion im Chat dar pocketmine.command.op.description = Gibt dem angegebenen Spieler den Operator-Status pocketmine.command.unban.cid.description = Erlaubt der angegebenen CID, sich wieder mit dem Server zu verbinden pocketmine.command.unban.player.description = Erlaubt dem angegebenen Spieler, sich wieder mit dem Server zu verbinden pocketmine.command.unban.ip.description = Erlaubt der angegebenen IP-Adresse, sich wieder mit dem Server zu verbinden pocketmine.command.save.description = Speichert den Server im Dateisystem pocketmine.command.saveoff.description = Deaktiviert das automatische Speichern des Servers pocketmine.command.saveon.description = Aktiviert das automatische Speichern des Servers pocketmine.command.say.description = Sendet die eingegebene Nachricht im Namen des Absenders an alle Spieler pocketmine.command.seed.description = Zeigt den Startwert (seed) der Welt pocketmine.command.setworldspawn.description = Legt den Spawnpunkt einer Welt fest. Werden keine Koordinaten angegeben, werden die des Spielers verwendet. pocketmine.command.spawnpoint.description = Legt den Spawnpunkt eines Spielers fest. pocketmine.command.stop.description = Beendet den Server pocketmine.command.tp.description = Teleportiert den angegebenen Spieler (oder dich selbst) zu einem anderen Spieler oder Koordinaten pocketmine.command.tell.description = Sendet dem angegebenen Spieler eine private Nachricht pocketmine.command.xp.description = Fügt dem angegebenen Spieler Erfahrungspunkte oder -level hinzu pocketmine.command.summon.description = Läßt ein Wesen am Ort des Spielers oder einem angegebenen Ort erscheinen pocketmine.command.fill.description = Füllt einen bestimmten Auswahlbereich mit Blöcken pocketmine.command.setblock.description = Ersetzt einen Block durch einen anderen Block pocketmine.command.weather.description = Legt das Wetter für eine Welt fest pocketmine.command.weather.usage = /weather pocketmine.command.weather.changed = Das Wetter in der Welt {%0} wurde erfolgreich gewechselt! pocketmine.command.weather.noregistered = Die Welt {%0} wurde nicht beim WetterManager registriert. pocketmine.command.weather.invalid = Ungültiges Wetter.(0,1,2,3) pocketmine.command.weather.wrong = Falsche Parameter. pocketmine.command.weather.invalid.level = Ungültiger Weltname. pocketmine.command.whitelist.description = Verwaltet die Liste der Spieler, denen es erlaubt ist, den Server zu benutzen pocketmine.crash.create = Es ist ein nicht behebbarer Fehler aufgetreten und der Server ist abgestürzt. Erstelle einen Crash-Dump... pocketmine.crash.error = Crash-Dump konnte nicht erstellt werden: {%0} pocketmine.crash.submit = Bitte lade die Datei "{%0}" in das Crash-Archiv hoch und übermittel den Link auf der Bug-Reporting Seite. Gib so viele Informationen an wie du kannst. pocketmine.crash.archive = Der Crash-Dump wurde automatisch in das Crash-Archiv übertragen. Du kannst in unter {%0} einsehen oder die ID #{%1} verwenden. pocketmine.debug.enable = LevelDB Unterstützung aktiviert pocketmine.player.invalidMove = {%0} bewegte sich falsch! pocketmine.player.logIn = {%0}[/{%1}:{%2}] [ClientID: {%3}] loggte sich mit der Entity-ID {%4} bei ({%5}, {%6}, {%7}, {%8}) ein pocketmine.player.logOut = {%0}[/{%1}:{%2}] loggte sich wegen {%3} aus pocketmine.player.transferred = {%0}[/{%1}:{%2}] wurde transferiert zu {%3} pocketmine.player.invalidEntity = {%0} versuchte, eine ungültige Entity zu attackieren pocketmine.plugin.load = Lade {%0} pocketmine.plugin.enable = Aktiviere {%0} pocketmine.plugin.disable = Deaktiviere {%0} pocketmine.plugin.restrictedName = Verbotener Name pocketmine.plugin.incompatibleAPI = Inkompatible API-Version pocketmine.plugin.unknownDependency = Unbekannte Abhängigkeit pocketmine.plugin.circularDependency = Zirkuläre Abhängigkeit erkannt pocketmine.plugin.genericLoadError = Plugin '{%0}' konnte nicht geladen werden pocketmine.plugin.spacesDiscouraged = Plugin '{%0}' verwendet Leerzeichen im Namen, was nicht erlaubt ist pocketmine.plugin.loadError = Plugin '{%0}' konnte nicht geladen werden: {%1} pocketmine.plugin.duplicateError = Plugin '{%0}' konnte nicht geladen werden: Plugin existiert bereits pocketmine.plugin.fileError = Konnte '{%0}' im Ordner '{%1}' nicht laden: {%2} pocketmine.plugin.commandError = Der Befehl {%0} konnte für das Plugin {%1} nicht geladen werden pocketmine.plugin.aliasError = Alias {%0} konnte für Plugin {%1} nicht geladen werden pocketmine.plugin.deprecatedEvent = Das Plugin '{%0}' hat einen Listener für '{%1}' auf die Methode '{%2}' registriert, aber das Event ist veraltet. pocketmine.plugin.eventError = "Das Event '{%0}' konnte nicht an '{%1}' weitergegeben werden: {%2} in {%3}" message.bed.sleep.night = Du kannst nur nachts schlafen! pocketmine.resourcepacks.createFolder = Pfad {%0} für Ressourcenpakete existiert nicht, erstelle einen neuen... pocketmine.resourcepacks.notFolder = Pfad {%0} existiert, ist aber kein Ordner pocketmine.resourcepacks.load = Lade Ressourcenpakete... pocketmine.resourcepacks.folderNotSupported = {%0} ist ein Ressourcenpaket im Ordner, welches noch nicht unterstützt wird. Bitte komprimiere es pocketmine.resourcepacks.unsupportedType = Typ des Ressourcenpakets {%0} is unbekannt und wird noch nicht unterstützt pocketmine.resourcepacks.packNotFound = Ressourcenpaket {%0} wurde nicht gefunden pocketmine.resourcepacks.loadFinished = Es wurden erfolgreich {%0} Rassourcenpakete geladen! # Language file compatible with Minecraft: Pocket Edition identifiers # # A message doesn't need to be there to be shown correctly on the client. # Only messages shown in PocketMine itself need to be here language.name = English language.selected = Selected {%0} ({%1}) as the base language multiplayer.player.joined = {%0} joined the game multiplayer.player.left = {%0} left the game chat.type.text = {%0} : {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0} has just earned the achievement {%1} disconnectionScreen.notAuthenticated = Xbox login required disconnectionScreen.outdatedClient = Outdated client! disconnectionScreen.outdatedServer = Outdated server! disconnectionScreen.serverFull = Server is full! disconnectionScreen.noReason = Disconnected from server disconnectionScreen.invalidSkin = Invalid skin! disconnectionScreen.invalidName = Invalid name! disconnectionScreen.refusedResourcePack = Downloading the resource pack is mandatory! disconnectionScreen.unavailableResourcePack = The resoucrce pack is unavailable on the server! death.fell.accident.generic = {%0} fell from a high place death.attack.inFire = {%0} went up in flames death.attack.onFire = {%0} burned to death death.attack.lava = {%0} tried to swim in lava death.attack.inWall = {%0} suffocated in a wall death.attack.drown = {%0} drowned death.attack.cactus = {%0} was pricked to death death.attack.generic = {%0} died death.attack.explosion = {%0} blew up death.attack.explosion.player = {%0} was blown up by {%1} death.attack.magic = {%0} was killed by magic death.attack.wither = {%0} withered away death.attack.mob = {%0} was slain by {%1} death.attack.player = {%0} was slain by {%1} death.attack.player.item = {%0} was slain by {%1} using {%2} death.attack.arrow = {%0} was shot by {%1} death.attack.arrow.item = {%0} was shot by {%1} using {%2} death.attack.fall = {%0} hit the ground too hard death.attack.outOfWorld = {%0} fell out of the world gameMode.survival = Survival Mode gameMode.creative = Creative Mode gameMode.adventure = Adventure Mode gameMode.spectator = Spectator Mode gameMode.changed = Your game mode has been updated potion.moveSpeed = Speed potion.moveSlowdown = Slowness potion.digSpeed = Haste potion.digSlowDown = Mining Fatigue potion.damageBoost = Strength potion.heal = Instant Health potion.harm = Instant Damage potion.jump = Jump Boost potion.confusion = Nausea potion.regeneration = Regeneration potion.resistance = Resistance potion.fireResistance = Fire Resistance potion.waterBreathing = Water Breathing potion.invisibility = Invisibility potion.blindness = Blindness potion.nightVision = Night Vision potion.hunger = Hunger potion.weakness = Weakness potion.poison = Poison potion.wither = Wither potion.healthBoost = Health Boost potion.absorption = Absorption potion.saturation = Saturation commands.generic.exception = An unknown error occurred while attempting to perform this command commands.generic.permission = You do not have permission to use this command commands.generic.notFound = Unknown command. Try /help for a list of commands commands.generic.player.notFound = That player cannot be found commands.generic.usage = Usage: {%0} commands.generic.level = level-name commands.generic.seed = seed-name commands.generic.name = name commands.generic.generator = generator-name commands.generic.opt.missing = Missing required properties, please confirm and re-enter. commands.generic.runingame = Please run this command in-game. commands.time.added = Added {%0} to the time commands.time.set = Set the time to {%0} commands.time.query = Time is {%0} commands.give.item.notFound = There is no such item with name {%0} commands.give.success = Given {%0} * {%1} to {%2} commands.give.tagError = Data tag parsing failed: {%0} commands.effect.notFound = There is no such mob effect with ID {%0} commands.effect.success = Given {%0} (ID {%1}) * {%2} to {%3} for {%4} seconds commands.effect.success.removed = Took {%0} from {%1} commands.effect.success.removed.all = Took all effects from {%0} commands.effect.failure.notActive = Couldn't take {%0} from {%1} as they do not have the effect commands.effect.failure.notActive.all = Couldn't take any effects from {%0} as they do not have any commands.enchant.maxLevel = Level Range of that enchantment is 1 - {%0} commands.enchant.noItem = The target is not holding an item commands.enchant.notFound = There is no such enchantment with ID {%0} commands.enchant.success = Enchanting succeeded commands.enchant.cantEnchant = The selected enchantment can't be added to the target item commands.particle.success = Playing effect {%0} for {%1} times commands.particle.notFound = Unknown effect name {%0} commands.players.list = There are {%0}/{%1} players online: commands.kill.successful = Killed {%0} commands.banlist.ips = There are {%0} total banned IP addresses: commands.banlist.players = There are {%0} total banned players: commands.banlist.cids = There are {%0} total banned CIDs: commands.defaultgamemode.success = The world's default game mode is now {%0} commands.op.success = Opped {%0} commands.deop.success = De-opped {%0} commands.seed.success = Seed: {%0} commands.bancidbyname.success = Banned player {%0}'s CID commands.bancid.success = Banned CID {%0} commands.unbancid.success = Unbanned CID {%0} commands.ban.success = Banned player {%0} commands.unban.success = Unbanned player {%0} commands.banip.invalid = You have entered an invalid IP address or a player that is not online commands.banip.success = Banned IP address {%0} commands.banip.success.players = Banned IP address {%0} belonging to {%1} commands.unbanip.invalid = You have entered an invalid IP address commands.unbanip.success = Unbanned IP address {%0} commands.banipbyname.success = Banned player {%0}'s IP commands.save.enabled = Turned on world auto-saving commands.save.disabled = Turned off world auto-saving commands.save.start = Saving... commands.save.success = Saved the world command.setblock.invalidBlock = Invalid block name/ID commands.stop.start = Stopping the server commands.kick.success = Kicked {%0} from the game commands.kick.success.reason = Kicked {%0} from the game: '{%1}' commands.tp.success = Teleported {%0} to {%1} commands.tp.success.coordinates = Teleported {%0} to {%1}, {%2}, {%3} commands.whitelist.list = There are {%0} (out of {%1} seen) whitelisted players: commands.whitelist.enabled = Turned on the whitelist commands.whitelist.disabled = Turned off the whitelist commands.whitelist.reloaded = Reloaded the whitelist commands.whitelist.add.success = Added {%0} to the whitelist commands.whitelist.add.usage = /whitelist add commands.whitelist.remove.success = Removed {%0} from the whitelist commands.whitelist.remove.usage = /whitelist remove commands.gamemode.success.self = Set own game mode to {%2} commands.gamemode.success.other = Set {%0}'s game mode to {%1} commands.gamemode.usage = /gamemode [player] commands.help.header = --- Showing help page {%0} of {%1} (/help ) --- commands.message.sameTarget = You can't send a private message to yourself! commands.difficulty.success = Set game difficulty to {%0} commands.spawnpoint.success = Set {%0}'s spawn point to ({%1}, {%2}, {%3}) commands.setworldspawn.success = Set the world spawn point to ({%0}, {%1}, {%2}) commands.xp.failure.withdrawXp = Cannot give player negative experience points commands.xp.success = Given {%0} experience to {%1} commands.xp.success.levels = Given {%0} levels to {%1} commands.xp.success.negative.levels = Taken {%0} levels from {%1} # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = Player data not found for "{%0}", creating new profile pocketmine.data.playerCorrupted = Corrupted data found for "{%0}", creating new profile pocketmine.data.playerOld = Old Player data found for "{%0}", upgrading profile pocketmine.data.saveError = Could not save player "{%0}": {%1} pocketmine.level.notFound = Level "{%0}" not found pocketmine.level.loadError = Could not load level "{%0}": {%1} pocketmine.level.generationError = Could not generate level "{%0}": {%1} pocketmine.level.tickError = Could not tick level "{%0}": {%1} pocketmine.level.chunkUnloadError = Error while unloading a chunk: {%0} pocketmine.level.backgroundGeneration = Spawn terrain for level "{%0}" is being generated in the background pocketmine.level.defaultError = No default level has been loaded pocketmine.level.preparing = Preparing level "{%0}" pocketmine.level.unloading = Unloading level "{%0}" pocketmine.server.start = Starting Minecraft: PE server version {%0} pocketmine.server.networkError = [Network] Stopped interface {%0} due to {%1} pocketmine.server.networkStart = Opening server on {%0}:{%1} pocketmine.server.info = This server is running {%0}{%1} "{%2}" (API {%3}) pocketmine.server.info.extended.title = -----Server information----- pocketmine.server.info.extended1 = This server is running {%0}{%1} ({%2}) {%3} pocketmine.server.info.extended2 = PHP version: {%0} pocketmine.server.info.extended3 = API: {%0} pocketmine.server.info.extended4 = Target client: Minecraft PE {%0} pocketmine.server.info.extended5 = Protocol version: {%0} pocketmine.server.license = {%0} is distributed under the GPL License version 3 and later pocketmine.server.tickOverload = Can't keep up! Is the server overloaded? pocketmine.server.startFinished = Done ({%0}s)! For help, type "help" or "?" pocketmine.server.defaultGameMode = Default game type: {%0} pocketmine.server.query.start = Starting GS4 status listener pocketmine.server.query.info = Setting query port to {%0} pocketmine.server.query.running = Query running on {%0}:{%1} pocketmine.command.alias.illegal = Could not register alias {%0} because it contains illegal characters pocketmine.command.alias.notFound = Could not register alias {%0} because it contains commands that do not exist: {%1} pocketmine.command.exception = Unhandled exception executing command '{%0}' in {%1}: {%2} pocketmine.commands.cave.usage = /cave [ ] | /cave getmypos pocketmine.commands.cave.info = Angle of rotation:{%0} Length:{%1} Branch Number:{%2} Strength:{%3} pocketmine.commands.cave.start = Generating cave, please wait pocketmine.commands.cave.success = Cave generated pocketmine.command.plugins.description = Gets a list of plugins running on the server pocketmine.command.plugins.success = Plugins ({%0}): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Reloads the server configuration and plugins pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Reloading server... pocketmine.command.reload.reloaded = Reload complete. pocketmine.command.lvdat.description = Change properties of a map. pocketmine.command.lvdat.changed = has changed {%1} of level "{%0}", some change need to reboot your server. pocketmine.command.lvdat.fixname = fixname successfully for level "{%0}", some change need to reboot your server. pocketmine.command.lvdat.nofound = level "{%0}" no found or load failed. pocketmine.command.lvdat.preset = Generator setting (preset) pocketmine.command.status.description = Reads back the server's performance. pocketmine.command.status.usage = /status pocketmine.command.status.title = Server status pocketmine.command.status.player = Player count: pocketmine.command.status.days = days pocketmine.command.status.hours = hours pocketmine.command.status.minutes = minutes pocketmine.command.status.seconds = seconds pocketmine.command.status.uptime = Uptime: pocketmine.command.status.AverageTPS = Average TPS: pocketmine.command.status.CurrentTPS = Current TPS: pocketmine.command.status.Networkupload = Network upload: pocketmine.command.status.Networkdownload = Network download: pocketmine.command.status.Threadcount = Thread count: pocketmine.command.status.Mainmemory = Main thread memory: pocketmine.command.status.Totalmemory = Total memory: pocketmine.command.status.Totalvirtualmemory = Total virtual memory: pocketmine.command.status.Heapmemory = Heap memory: pocketmine.command.status.Maxmemorysystem = Maximum memory (system): pocketmine.command.status.Maxmemorymanager = Maximum memory (manager): pocketmine.command.status.World = World pocketmine.command.status.chunks = chunks, pocketmine.command.status.entities = entities, pocketmine.command.status.tiles = tiles. pocketmine.command.status.Time = Time pocketmine.command.status.ms = ms pocketmine.command.gc.description = Fires garbage collection tasks pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = Collection Report pocketmine.command.gc.chunks = Chunks: pocketmine.command.gc.entities = Entities: pocketmine.command.gc.tiles = Tiles: pocketmine.command.gc.cycles = Cycles: pocketmine.command.gc.memory = Release memory: pocketmine.command.biome.description = Change the biome of the area.(To change snow or rain) pocketmine.command.biome.posset = Set position {%3} at ({%1},{%2}) in level {%0} pocketmine.command.biome.get = The ID of biome you are in is {%0}. Color: {%1},{%2},{%3} pocketmine.command.biome.wrongLev = Cannot set position in different level. pocketmine.command.biome.wrongBio = Wrong ID of biome. e.g. 1 (Plains), 2 (Desert),13 (Ice Mountains),6 (Swampland) pocketmine.command.biome.wrongCol = Wrong Color. e.g. 146,188,89 .Use "/biome get" to get other color. pocketmine.command.biome.noPos = Please use "/biome pos1|pos2" to select the area first. pocketmine.command.biome.set = Set the selected area's biome to {%0} pocketmine.command.biome.color = Set the grass colour to {%0},{%1},{%2} pocketmine.command.timings.description = Records timings to see performance of the server. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Enabled Timings & Reset pocketmine.command.timings.disable = Disabled Timings pocketmine.command.timings.timingsDisabled = Please enable timings by typing /timings on pocketmine.command.timings.reset = Timings reset pocketmine.command.timings.pasteError = An error happened while pasting the report pocketmine.command.timings.timingsUpload = Timings uploaded to {%0} pocketmine.command.timings.timingsRead = You can read the results at {%0} pocketmine.command.timings.timingsWrite = Timings written to {%0} pocketmine.command.version.description = Gets the version of this server including any plugins in use pocketmine.command.version.usage = /version [plugin name] pocketmine.command.version.noSuchPlugin = This server is not running any plugin by that name. Use /plugins to get a list of plugins. pocketmine.command.give.description = Gives the specified player a certain amount of items pocketmine.command.give.usage = /give [amount] [tags...] pocketmine.command.kill.description = Commit suicide or kill other players pocketmine.command.kill.usage = /kill [player] pocketmine.command.particle.description = Adds particles to a world pocketmine.command.particle.usage = /particle [count] [data] pocketmine.command.time.description = Changes the time on each world pocketmine.command.time.usage = /time OR /time pocketmine.command.bancidbyname.description = Prevents the specified CID by name from using this server pocketmine.command.bancidbyname.usage = /bancidbyname pocketmine.command.bancid.description = Prevents the specified CID from using this server pocketmine.command.bancid.usage = /bancid pocketmine.command.banipbyname.description = Prevents the specified IP address by name from using this server pocketmine.command.banipbyname.usage = /banipbyname pocketmine.command.ban.player.description = Prevents the specified player from using this server pocketmine.command.ban.player.ban.usage = /ban [reason ...] [time(day)] pocketmine.command.banip.description = Prevents the specified IP address from using this server pocketmine.command.banip.usage = /ban-ip [reason ...] pocketmine.command.banlist.description = View all players banned from this server pocketmine.command.banlist.usage = /banlist [ips|players|cids] pocketmine.command.defaultgamemode.description = Set the default gamemode pocketmine.command.defaultgamemode.usage = /defaultgamemode pocketmine.command.deop.description = Takes the specified player's operator status pocketmine.command.op.usage = /op pocketmine.command.deop.usage = /deop pocketmine.command.difficulty.description = Sets the game difficulty pocketmine.command.difficulty.usage = /difficulty pocketmine.command.enchant.description = Adds enchantments on items pocketmine.command.enchant.usage = /enchant [level] pocketmine.command.effect.description = Adds/Removes effects on players pocketmine.command.effect.usage = /effect [seconds] [amplifier] [hideParticles] OR /effect clear pocketmine.command.gamemode.description = Changes the player to a specific game mode pocketmine.command.gamemode.usage = /gamemode [player] pocketmine.command.help.description = Shows the help menu pocketmine.command.help.usage = /help [page|command name] pocketmine.command.kick.description = Removes the specified player from the server pocketmine.command.kick.usage = /kick [reason ...] pocketmine.command.list.description = Lists all online players pocketmine.command.players.usage = /list pocketmine.command.me.description = Performs the specified action in chat pocketmine.command.me.usage = /me pocketmine.command.op.description = Gives the specified player operator status pocketmine.command.unban.cid.description = Allows the specified CID to use this server pocketmine.command.unban.cid.usage = /pardoncid pocketmine.command.unban.player.description = Allows the specified player to use this server pocketmine.command.unban.player.usage = /pardon pocketmine.command.unban.ip.description = Allows the specified IP address to use this server pocketmine.command.unban.ip.usage = /pardon-ip
pocketmine.command.save.description = Saves the server to disk pocketmine.command.save.usage = /save-all pocketmine.command.saveoff.description = Disables server autosaving pocketmine.command.saveoff.usage = /save-off pocketmine.command.saveon.description = Enables server autosaving pocketmine.command.saveon.usage = /save-on pocketmine.command.say.description = Broadcasts the given message as the sender pocketmine.command.say.usage = /say pocketmine.command.seed.description = Shows the world seed pocketmine.command.seed.usage = /seed pocketmine.command.setworldspawn.description = Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. pocketmine.command.setworldspawn.usage = /setworldspawn [ ] pocketmine.command.spawnpoint.description = Sets a player's spawn point pocketmine.command.spawnpoint.usage = /spawnpoint [player] [ ] pocketmine.command.stop.description = Stops the server pocketmine.command.stop.usage = /stop pocketmine.command.tp.description = Teleports the given player (or yourself) to another player or coordinates pocketmine.command.tp.usage = /tp [target player] OR /tp [target player] [ ] pocketmine.command.tell.description = Sends a private message to the given player pocketmine.command.tell.usage = /tell pocketmine.command.xp.description = Add experience or experience level to the given player pocketmine.command.xp.usage = /xp [player] OR /xp L [player] pocketmine.command.summon.description = Summons a entity at the player's location or a specific location pocketmine.command.summon.usage = /summon [entity] [ ] [NBTTag] pocketmine.command.fill.description = fills a specific selection with blocks pocketmine.command.setblock.description = Changes a block to another block pocketmine.command.setblock.usage = /setblock [damage] pocketmine.command.weather.description = Set weather for level pocketmine.command.weather.usage = /weather pocketmine.command.weather.changed = Weather changed successfully in level {%0}! pocketmine.command.weather.noregistered = level {%0} hasn't registered to WeatherManager. pocketmine.command.weather.invalid = Invalid weather.(0,1,2,3) pocketmine.command.weather.wrong = Wrong parameters. pocketmine.command.weather.invalid.level = Invalid level name. pocketmine.command.whitelist.description = Manages the list of players allowed to use this server pocketmine.command.whitelist.usage = /whitelist pocketmine.crash.create = An unrecoverable error has occurred and the server has crashed. Creating a crash dump pocketmine.crash.error = Could not create crash dump: {%0} pocketmine.crash.submit = Please upload the "{%0}" file to the Crash Archive and submit the link to the Bug Reporting page. Give as much info as you can. pocketmine.crash.archive = The crash dump has been automatically submitted to the Crash Archive. You can view it on {%0} or use the ID #{%1}. pocketmine.debug.enable = LevelDB support enabled pocketmine.player.invalidMove = {%0} moved wrongly! pocketmine.player.logIn = {%0}[/{%1}:{%2}] [ClientID: {%3}] logged in with entity id {%4} at ({%5}, {%6}, {%7}, {%8}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] logged out due to {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}] was transferred to {%3} pocketmine.player.invalidEntity = {%0} tried to attack an invalid entity pocketmine.plugin.load = Loading {%0} pocketmine.plugin.enable = Enabling {%0} pocketmine.plugin.disable = Disabling {%0} pocketmine.plugin.restrictedName = Restricted name pocketmine.plugin.incompatibleAPI = Incompatible API version pocketmine.plugin.unknownDependency = Unknown dependency pocketmine.plugin.circularDependency = Circular dependency detected pocketmine.plugin.genericLoadError = Could not load plugin '{%0}' pocketmine.plugin.spacesDiscouraged = Plugin '{%0}' uses spaces in its name, this is discouraged pocketmine.plugin.loadError = Could not load plugin '{%0}': {%1} pocketmine.plugin.duplicateError = Could not load plugin '{%0}': plugin exists pocketmine.plugin.fileError = Could not load '{%0}' in folder '{%1}': {%2} pocketmine.plugin.commandError = Could not load command {%0} for plugin {%1} pocketmine.plugin.aliasError = Could not load alias {%0} for plugin {%1} pocketmine.plugin.deprecatedEvent = Plugin '{%0}' has registered a listener for '{%1}' on method '{%2}', but the event is Deprecated. pocketmine.plugin.eventError = "Could not pass event '{%0}' to '{%1}': {%2} on {%3} message.bed.sleep.night = You can only sleep at night pocketmine.resourcepacks.createFolder = Path {%0} for resource packs does not exist, creating a new folder... pocketmine.resourcepacks.notFolder = Path {%0} for resource packs exist, but it is not a folder pocketmine.resourcepacks.load = Loading resource packs... pocketmine.resourcepacks.folderNotSupported = {%0} is a resource pack in a folder, which is not supported yet. Please compress it pocketmine.resourcepacks.unsupportedType = Type of resource pack {%0} is unknown and is not supported yet pocketmine.resourcepacks.packNotFound = Resource pack {%0} not found pocketmine.resourcepacks.loadFinished = Successfully loaded {%0} resource pack(s) # Fichier de langue compatible avec Minecraft: Pocket Edition # Aidez moi a traduire (ou à vérifier) les lignes: 186, 212, 276, 277, 286, 290, 295. # Un message n'a pas besoin d'être là pour être montré correctement sur le client. # Seuls les messages affichés dans Genisys se doivent d'être ici language.name = Français language.selected = Sélection de {%0} ({%1}) comme langue de base multiplayer.player.joined = {%0} a rejoint la partie multiplayer.player.left = {%0} a quitté la partie chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0} vient d'obtenir le succès {%1} disconnectionScreen.outdatedClient = Client expiré ! disconnectionScreen.outdatedServer = Serveur expiré ! disconnectionScreen.serverFull = Le serveur est complet ! disconnectionScreen.noReason = Déconnecté du serveur disconnectionScreen.invalidSkin = Skin invalide ! disconnectionScreen.invalidName = Nom invalide ! death.fell.accident.generic = {%0} a fait une terrible chute death.attack.inFire = {%0} a péri dans les flammes death.attack.onFire = {%0} s'est fait carboniser à mort death.attack.lava = {%0} a essayé de nager dans la lave death.attack.inWall = {%0} a suffoqué dans un mur death.attack.drown = {%0} s'est noyé death.attack.cactus = {%0} s'est fait piquer à mort death.attack.generic = {%0} est mort death.attack.explosion = {%0} est mort dans une explosion death.attack.explosion.player = {%0} est mort dans une explosion causé par {%1} death.attack.magic = {%0} a été tué par la magie death.attack.wither = {%0} sécha death.attack.mob = {%0} a été tué par {%1} death.attack.player = {%0} a été tué par {%1} death.attack.player.item = {%0} a été tué par {%1} en utilisant {%2} death.attack.arrow = {%0} s'est fait tiré dessus par {%1} death.attack.arrow.item = {%0} s'est fait tiré dessus par {%1} en utilisant {%2} death.attack.fall = {%0} a frappé le sol trop rapidement death.attack.outOfWorld = {%0} a littéralement quitté notre monde gameMode.survival = Mode Survie gameMode.creative = Mode Créatif gameMode.adventure = Mode Aventure gameMode.spectator = Mode Spectateur gameMode.changed = Votre mode de jeu a été mis à jour potion.moveSpeed = Vitesse potion.moveSlowdown = Lenteur potion.digSpeed = Célérité potion.digSlowDown = Fatigue potion.damageBoost = Force potion.heal = Vie instantanée potion.harm = Dégats instantanés potion.jump = Saut potion.confusion = Nausée potion.regeneration = Régénération potion.resistance = Résistance potion.fireResistance = Résistance au feu potion.waterBreathing = Respiration aquatique potion.invisibility = Invisibilité potion.blindness = Cécité potion.nightVision = Vision nocturne potion.hunger = Faim potion.weakness = Faiblesse potion.poison = Poison potion.wither = Wither potion.healthBoost = Soin instantané potion.absorption = Absorption potion.saturation = Satiété commands.generic.exception = Une erreur inconnue est survenue lors de la tentative d'exécution de cette commande commands.generic.permission = Vous n'avez pas la permission d'utiliser cette commande commands.generic.notFound = Cette commande n'est pas reconnue : faites /help pour obtenir la liste des commandes commands.generic.player.notFound = Le joueur n'a pas été trouvé commands.generic.usage = Utilisation : {%0} commands.generic.level = Monde commands.generic.seed = Semence commands.generic.name = Nom commands.generic.generator = Générateur commands.generic.opt.missing = Propriétés manquantes, merci de confirmer et d'appuyer sur entrée. commands.generic.runingame = Cette commande ne fonctionne qu'en jeu. commands.time.added = Heure avancée de {%0} commands.time.set = Heure fixée à {%0} commands.time.query = Temps : {%0} commands.me.usage = /me commands.give.item.notFound = Il n'y a pas d'objet connu ayant pour nom {%0} commands.give.success = Don de {%0} * {%1} à {%2} commands.give.tagError = L'analyse syntaxique du data tag a échoué : {%0} commands.effect.usage = /effect [temps] [amplificateur] [masquer les particules] OU /effect clear commands.effect.notFound = Il n'y a pas d'effet connu ayant pour ID {%0} commands.effect.success = Don de l'effet {%0} (ID {%1}) * {%2} à {%3} pour une durée de {%4} secondes commands.effect.success.removed = Retrait de l'effet {%0} de {%1} commands.effect.success.removed.all = Retrait de tous les effets de {%0} commands.effect.failure.notActive = Impossible de supprimer l'effet {%0} de {%1} car ce joueur ne possède pas cet effet commands.effect.failure.notActive.all = Impossible de supprimer les effets du joueur {%0} car ce joueur ne possède aucun effets commands.enchant.noItem = Le joueur n'a pas d'objet en main commands.enchant.notFound = Il n'y a pas d'enchantement avec l'ID {%0} commands.enchant.success = Enchantement réussi commands.enchant.cantEnchant = L'enchantement sélectionné ne peut être ajouté à l'élément cible commands.enchant.usage = /enchant [niveau] commands.particle.success = L'effet {%0} apparaît {%1} fois commands.particle.notFound = Effet inconnu {%0} commands.players.usage = /list commands.players.list = Il y'a {%0}/{%1} joueurs en ligne: commands.kill.successful = {%0} a été anéanti(e) commands.banlist.ips = Il y'a un total de {%0} IP banni(s) : commands.banlist.players = Il y'a un total de {%0} joueur(s) banni(s) : commands.banlist.cids = Il y'a un total de {%0} CID banni(s) : commands.banlist.usage = /banlist [ip|players|cids] commands.defaultgamemode.usage = /defaultgamemode commands.defaultgamemode.success = Le mode de jeu par défaut est maintenant {%0} commands.op.success = {%0} a été promu opérateur commands.op.usage = /op commands.deop.success = {%0} a perdu ses privilèges d'opérateur commands.deop.usage = /deop commands.say.usage = /say commands.seed.usage = /seed commands.seed.success = Semence : {%0} commands.bancidbyname.success = CID du joueur {%0} banni commands.bancidbyname.usage = /bancidbyname commands.bancid.success = CID {%0} banni commands.bancid.usage = /bancid commands.unbancid.usage = /pardoncid commands.unbancid.success = CID {%0} débanni commands.ban.success = Joueur {%0} banni commands.ban.usage = /ban [raison ...] [temps(jour)] commands.unban.success = Joueur {%0} débanni commands.unban.usage = /pardon commands.banip.invalid = Vous avez entré une adresse IP invalide ou un joueur qui n'est pas connecté. commands.banip.success = Adresse IP {%0} bannie commands.banip.success.players = L'adresse IP {%0} appartenant à {%1} a était bannie commands.banip.usage = /ban-ip [raison ...] commands.unbanip.invalid = Vous avez entré une adresse IP invalide commands.unbanip.success = Adresse IP {%0} débannie commands.unbanip.usage = /pardon-ip commands.banipbyname.success = L'adresse IP du joueur {%0} a été bannie commands.banipbyname.usage = /banipbyname commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = Sauvegarde automatique du monde activée commands.save.disabled = Sauvegarde automatique du monde désactivée commands.save.start = Sauvegarde... commands.save.success = Monde sauvegardé commands.setblock.usage = /setblock [dégâts] command.setblock.invalidBlock = Vous avez entré un nom/ID de bloc invalide commands.stop.usage = /stop commands.stop.start = Arrêt du serveur commands.kick.success = {%0} s'est fait éjecter du serveur commands.kick.success.reason = {%0} s'est fait éjecter du serveur pour la raison suivante : {%1}' commands.kick.usage = /kick [raison ...] commands.tp.success = {%0} s'est fait téléporter vers {%1} commands.tp.success.coordinates = {%0} s'est fait téléporter en {%1}, {%2}, {%3} commands.tp.usage = /tp [joueur cible] OU /tp [joueur cible] [ ] commands.whitelist.list = Il y a {%0} joueur(s) (sur {%1} détecté(s)) dans la liste blanche : commands.whitelist.enabled = Liste blanche activée commands.whitelist.disabled = Liste blanche désactivée commands.whitelist.reloaded = Liste blanche réactualisée commands.whitelist.add.success = {%0} a été ajouté(e) à la liste blanche commands.whitelist.add.usage = /whitelist add commands.whitelist.remove.success = Le joueur {%0} a été retiré(e) de la liste blanche commands.whitelist.remove.usage = /whitelist remove commands.whitelist.usage = /whitelist commands.gamemode.success.self = Votre mode de jeu a été changé en Mode {%2} commands.gamemode.success.other = Le mode de jeu de {%0} a été changé en Mode {%1} commands.gamemode.usage = /gamemode [joueur] commands.help.header = --- Affichage de la page d'aide {%0} sur {%1} (/help ) --- commands.help.usage = /help [page|nom de la commande] commands.message.usage = /tell commands.message.sameTarget = Vous ne pouvez pas vous envoyer de message privé ! commands.xp.usage = /xp commands.difficulty.usage = /difficulty commands.difficulty.success = La difficulté a été changée en {%0} commands.spawnpoint.usage = /spawnpoint [joueur] [ ] commands.spawnpoint.success = Le nouveau point d'apparition de {%0} a été défini en ({%1}, {%2}, {%3}) commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = Le point d'appartition du monde a été défini en ({%0}, {%1}, {%2}) commands.summon.usage = /summon [nom de l'entité] [ ] [dataTag] # -------------------- Fichiers de langue PocketMine, uniquement pour la console -------------------- pocketmine.data.playerNotFound = Données du joueur "{%0}" invalides , création d'un nouveau profil pocketmine.data.playerCorrupted = Données du joueur "{%0}" corrompus, création d'un nouveau profil pocketmine.data.playerOld = Ancienne données du joueur "{%0}" trouvées, mise à jour du profil pocketmine.data.saveError = Impossible de sauvegarder le joueur "{%0}": {%1} pocketmine.level.notFound = Le monde "{%0}" est introuvable pocketmine.level.loadError = Impossible de charger le monde "{%0}": {%1} pocketmine.level.generationError = Impossible de générer le monde "{%0}": {%1} pocketmine.level.tickError = Impossible de vérifier le monde "{%0}": {%1} pocketmine.level.chunkUnloadError = Une erreur est survenue lors du déchargement d'un chunk: {%0} pocketmine.level.backgroundGeneration = L'apparition du terrain pour le niveau "{%0}" est entrain de ce générer en arrière-plan pocketmine.level.defaultError = Aucun niveau par défaut n'a été chargée pocketmine.level.preparing = Préparation du terrain "{%0}" pocketmine.level.unloading = Déchargement du terrain "{%0}" pocketmine.server.start = Démarrage du serveur Minecraft PE version {%0} pocketmine.server.networkError = [Réseau] Arrêt de l'interface {%0} à cause de {%1} pocketmine.server.networkStart = Ouverture du serveur sur {%0}:{%1} pocketmine.server.info = Ce serveur fonctionne sur {%0} version {%1} "{%2}" (API {%3}) pocketmine.server.info.extended = Ce serveur fonctionne sur {%0} {%1} "{%2}" API implantée {%3} pour Minecraft: Pocket Edition {%4} (version du protocol {%5}) pocketmine.server.license = {%0} est distribué sous la licence GPL version 3 et plus pocketmine.server.tickOverload = Désynchronisation ! Est-ce que le serveur surchargé ? pocketmine.server.startFinished = Démarrer en ({%0}s)! Pour afficher l'aide tapez "/help" ou "/?" pocketmine.server.defaultGameMode = Mode de jeu par défaut : {%0} pocketmine.server.query.start = Démarrage de GS4 statut listener pocketmine.server.query.info = Réglage du port Query sur {%0} pocketmine.server.query.running = Le Query fonctionne sur {%0}:{%1} pocketmine.command.alias.illegal = Le pseudonyme {%0} ne peut pas être enregistré car il contient des caractères illégaux pocketmine.command.alias.notFound = Le pseudonyme {%0} ne peut pas être enregistré car il contient des commandes qui n'existent pas: {%1} pocketmine.command.exception = Commande d'exécution d'exception non gérée '{%0}' dans {%1}: {%2} pocketmine.commands.cave.usage = /cave | /cave getmypos pocketmine.commands.cave.info = Angle de rotation:{%0} Longueur:{%1} Branch Number:{%2} Force:{%3} pocketmine.commands.cave.start = Génération de la caverne, veuillez patientez... pocketmine.commands.cave.success = Caverne généré pocketmine.command.plugins.description = Obtention de la liste des plugins en cours d'exécution sur le serveur pocketmine.command.plugins.success = Plugins ({%0}) : {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Recharge la configuration du serveur et des plugins pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Rechargement du serveur... pocketmine.command.reload.reloaded = Rechargement du serveur terminé. pocketmine.command.lvdat.description = Modification des propriétés de la carte. pocketmine.command.lvdat.changed = Changement {%1} du monde "{%0}", les changements nécessites un redémarrage de serveur. pocketmine.command.lvdat.fixname = Changement du nom terminé "{%0}", les changements nécessites un redémarrage de serveur. pocketmine.command.lvdat.nofound = le monde "{%0}" ne fonctionne pas ou le chargement a échoué pocketmine.command.lvdat.preset = Réglage du générateur (prédéfini) pocketmine.command.status.description = Relecture des performances du serveur. pocketmine.command.status.usage = /status pocketmine.command.status.title = Statut du serveur pocketmine.command.status.player = Nombre de joueur(s): pocketmine.command.status.days = jour(s) pocketmine.command.status.hours = heure(s) pocketmine.command.status.minutes = minute(s) pocketmine.command.status.seconds = seconde(s) pocketmine.command.status.uptime = Durée de fonctionnement: pocketmine.command.status.AverageTPS = Moyenne de TPS: pocketmine.command.status.CurrentTPS = TPS actuel: pocketmine.command.status.Networkupload = Débit montant de réseau : pocketmine.command.status.Networkdownload = Débit descendant de réseau : pocketmine.command.status.Threadcount = Thread count: pocketmine.command.status.Mainmemory = Main thread memory: pocketmine.command.status.Totalmemory = Mémoire totale: pocketmine.command.status.Totalvirtualmemory = Mémoire virtuelle totale: pocketmine.command.status.Heapmemory = Mémoire utilisée: pocketmine.command.status.Maxmemorysystem = Mémoire maximale (système): pocketmine.command.status.Maxmemorymanager = Mémoire maximale (gestionnaire): pocketmine.command.status.World = Monde pocketmine.command.status.chunks = Chunks, pocketmine.command.status.entities = Entités, pocketmine.command.status.tiles = Tiles. pocketmine.command.status.Time = Temps pocketmine.command.status.ms = ms pocketmine.command.gc.description = Ramassage des déchets pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = Collection du Rapport pocketmine.command.gc.chunks = Chunks: pocketmine.command.gc.entities = Entités: pocketmine.command.gc.tiles = Tiles: pocketmine.command.gc.cycles = Cycles: pocketmine.command.gc.memory = Release memory: pocketmine.command.biome.description = Changez le biome de la zone.(Pour changer la neige ou la pluie) pocketmine.command.biome.posset = Réglage de la position {%3} à ({%1},{%2}) dans le niveau {%0} pocketmine.command.biome.get = L'ID de biome où vous êtes est {%0}. Couleurr: {%1},{%2},{%3} pocketmine.command.biome.wrongLev = Impossible de définir cette position dans un niveau différent. pocketmine.command.biome.wrongBio = Mauvais ID de biome. e.g. 1 (Plaine), 2 (Désert),13 (Montagnes de glace),6 (Marécage) pocketmine.command.biome.wrongCol = Mauvaise couleur. e.g. 146,188,89 .Use "/biome get" pour obtenir une autre couleur. pocketmine.command.biome.noPos = Veuillez utiliser "/biome pos1|pos2" pour sélectionner la première zone. pocketmine.command.biome.set = Réglage du biome dans la zone sélectionnée à {%0} pocketmine.command.biome.color = Réglage de la couleur de l'herbe à {%0},{%1},{%2} pocketmine.command.timings.description = Synchronisation des rapports pour voir les performances du serveur. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Activer le minutage et réinitialiser pocketmine.command.timings.disable = Désactiver le minutage pocketmine.command.timings.timingsDisabled = Merci d'activer les rapports de performance en tapant la commande /timings on pocketmine.command.timings.reset = Minutage réinitialisé pocketmine.command.timings.pasteError = Une erreur s'est produite lors de la génération du rapport pocketmine.command.timings.timingsUpload = Rapports de performance mis à jours sur {%0} pocketmine.command.timings.timingsRead = Vous pouvez lire les résultats dans {%0} pocketmine.command.timings.timingsWrite = Rapports de performance écrits sur {%0} pocketmine.command.version.description = Obtenez la version de ce serveur, y compris tous les plugins utilisés pocketmine.command.version.usage = /version [nom du plugin] pocketmine.command.version.noSuchPlugin = Ce serveur n'utilise pas ce plugin. Utilisez / plugins pour obtenir la liste des plugins. pocketmine.command.give.description = Donne au joueur spécifié une certaine quantité d'items pocketmine.command.give.usage = /give [montant] [tags...] pocketmine.command.kill.description = Se suicider ou tuer d'autres joueurs pocketmine.command.kill.usage = /kill [joueur] pocketmine.command.particle.description = Ajoute des particules à un monde pocketmine.command.particle.usage = /particle [nombre] [data] pocketmine.command.time.description = Change le temps sur chaque monde pocketmine.command.time.usage = /time OU /time pocketmine.command.bancidbyname.description = Empêche le CID du joueur spécifié d'accéder à ce serveur pocketmine.command.bancid.description = Empêche le CID spécifié d'accéder à ce serveur pocketmine.command.banipbyname.description = Empêche l'adresse IP du joueur spécifiée d'accéder a ce serveur pocketmine.command.ban.player.description = Empêche le joueur spécifié d'accéder a ce serveur pocketmine.command.ban.ip.description = Empêche l'adresse IP spécifiée d'accéder a ce serveur pocketmine.command.banlist.description = Voir la liste de tous les joueurs bannis sur ce serveur pocketmine.command.defaultgamemode.description = Réglez le mode de jeu par défaut pocketmine.command.deop.description = Enléve les droits d'administration à un joueur spécifié. pocketmine.command.difficulty.description = Définit la difficulté du jeu pocketmine.command.enchant.description = Ajoute un enchantement sur un item pocketmine.command.effect.description = Ajoute/Supprime des effets sur un joueur spécifié pocketmine.command.gamemode.description = Change le mode de jeu d'un joueur spécifique pocketmine.command.help.description = Affiche le menu d'aide pocketmine.command.kick.description = Exclu le joueur spécifié du serveur pocketmine.command.list.description = Liste de tous les joueurs en ligne pocketmine.command.me.description = Exécute l'action spécifiée dans le chat pocketmine.command.op.description = Donne le statut d'opérateur au joueur spécifié pocketmine.command.unban.cid.description = Autorise le CID spécifié à accéder au serveur pocketmine.command.unban.player.description = Autorise le joueur spécifié à accéder au serveur pocketmine.command.unban.ip.description = Autorise l'adresse IP spécifiée à accéder au serveur pocketmine.command.save.description = Sauvegarde le serveur sur le disque pocketmine.command.saveoff.description = Désactive la sauvegarde automatique pocketmine.command.saveon.description = Active la sauvegarde automatique pocketmine.command.say.description = Diffuse un message dans le chat pocketmine.command.seed.description = Affiche le seed du serveur pocketmine.command.setworldspawn.description = Définit le point d'apparition du monde.Si aucunes coordonnées ne sont spécifiées, les coordonnées du joueur sont utilisés. pocketmine.command.spawnpoint.description = Définit le point d'apparition d'un joueur pocketmine.command.stop.description = Arrête le serveur pocketmine.command.tp.description = Téléporte le joueur donné (ou vous-même) à un autre joueur ou vers des coordonnées pocketmine.command.tell.description = Envoie un message privé au joueur donné pocketmine.command.xp.description = Ajouter de l'expérience a un joueur donné pocketmine.command.summon.description = Invoque une entité à l'emplacement d'un joueur ou à un emplacement spécifique pocketmine.command.fill.description = remplit une sélection spécifique avec des blocs pocketmine.command.setblock.description = Remplace un bloc par un autre pocketmine.command.weather.description = Réglez la météo pour le niveau pocketmine.command.weather.usage = /weather pocketmine.command.weather.changed = Météo changé avec succès au niveau {%0}! pocketmine.command.weather.noregistered = Le niveau {%0} n'est pas enregistré à WeatherManager. pocketmine.command.weather.invalid = Météo Invalide.(0,1,2,3) pocketmine.command.weather.wrong = Mauvais paramètres. pocketmine.command.weather.invalid.level = Nom du niveau invalide. pocketmine.command.whitelist.description = Gère la liste des joueurs autorisés à accéder à serveur pocketmine.crash.create = Une erreur irréversible s'est produite causant un crash du serveur. Création d'un rapport de crash pocketmine.crash.error = Impossible de créer un fichier de crash : {%0} pocketmine.crash.submit = Soumettez le fichier "{%0}" à l'archive des crashs et soumettez le lien vers la page des rapports de bugs. Donnez le plus d'informations possible. pocketmine.crash.archive = Le rapport de crash a été automatiquement soumis à l'archive des crashs. Vous pouvez le voir sur {%0} ou utilisez l'ID #{%1}. pocketmine.debug.enable = Support LevelDB activé pocketmine.player.invalidMove = {%0} se déplace incorrectement ! pocketmine.player.logIn = {%0}[/{%1}:{%2}] [IDClient: {%3}] s'est connecté avec l'ID d'entité {%4} à ({%5}, {%6}, {%7}, {%8}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] déconnecté en raison de {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}] a été transféré à {%3} pocketmine.player.invalidEntity = {%0} essaye d'attaquer une entité invalide pocketmine.plugin.load = Chargement {%0} pocketmine.plugin.enable = Activation de {%0} pocketmine.plugin.disable = Désactivation de {%0} pocketmine.plugin.restrictedName = Nom restreint pocketmine.plugin.incompatibleAPI = Version d'API Incompatible pocketmine.plugin.unknownDependency = Dépendance inconnu pocketmine.plugin.circularDependency = Dépendance circulaire détectée pocketmine.plugin.genericLoadError = Impossible de charger le plugin '{%0}' pocketmine.plugin.spacesDiscouraged = Le plugin '{%0} utilise des espaces dans son nom, ceci est déconseillé pocketmine.plugin.loadError = Impossible de charger le plugin '{%0}': {%1} pocketmine.plugin.duplicateError = Impossible de charger le plugin '{%0}': le plugin existe pocketmine.plugin.fileError = Impossible de charger '{%0}' dans le dossier '{%1}': {%2} pocketmine.plugin.commandError = Impossible de charger la commande {%0} pour le plugin {%1} pocketmine.plugin.aliasError = L'alias {%0} ne peut pas être chargé pour le plugin {%1} pocketmine.plugin.deprecatedEvent = Le plugin '{%0}' a enregistré un auditeur pour '{%1} "sur la méthode'{%2}', mais l'événement est obsolète. pocketmine.plugin.eventError = "Impossible de passer l’événement '{%0}' de '{%1}': {%2} sur {%3}" ; Language file compatible with Minecraft: Pocket Edition identifiers ; ; A message doesn't need to be there to be shown correctly on the client. ; Only messages shown in PocketMine itself need to be here language.name = Bahasa Indonesia language.selected = Pilih {%0} ({%1}) menjadi dasar bahasa multiplayer.player.joined = {%0} Bergabung ke dalam permainan multiplayer.player.left = {%0} keluar dari permainan chat.type.achievement = {%0} mendapatkan penghargaan {%1} disconnectionScreen.outdatedClient = Versi Game Tidak Mendukung! disconnectionScreen.outdatedServer = Server menggunakan versi game yang berbeda! disconnectionScreen.serverFull = Server Penuh! disconnectionScreen.noReason = Terputus dari server disconnectionScreen.invalidSkin = Kesalahan kulit! disconnectionScreen.invalidName = Kesalahan nama! death.fell.accident.generic = {%0} Jatuh dari ketinggian death.attack.inFire = {%0} terbakar death.attack.onFire = {%0} mati terbakar death.attack.lava = {%0} mencoba berenang di lava death.attack.inWall = {%0} terjepit death.attack.drown = {%0} tenggelam death.attack.cactus = {%0} mati tertusuk death.attack.generic = {%0} mati death.attack.explosion = {%0} meledak death.attack.explosion.player = {%0} di ledakan oleh {%1} death.attack.magic = {%0} terbunuh dengan sihir death.attack.wither = {%0} terkena wither death.attack.mob = {%0} Dibunuh oleh {%1} death.attack.player = {%0} Dibunuh oleh {%1} death.attack.player.item = {%0} Dibunuh oleh {%1} menggunakan {%2} death.attack.arrow = {%0} Terpanah oleh {%1} death.attack.arrow.item = {%0} Terpanah oleh {%1} menggunakan {%2} death.attack.fall = {%0} Terjatuh terlalu keras death.attack.outOfWorld = {%0} Jatuh ke void gameMode.survival = Mode Bertahan Hidup gameMode.creative = Mode Kreatif gameMode.adventure = Mode Petualang gameMode.spectator = Mode Penglihat gameMode.changed = Mode permainan anda berubah potion.moveSpeed = Kecepatan potion.moveSlowdown = Kelambatan potion.digSpeed = Kecepatan menambang potion.digSlowDown = Kelambatan menambang potion.damageBoost = Kekuatan potion.heal = Kesehatan instan potion.harm = Serangan instan potion.jump = Kekuatan loncat potion.confusion = Mual potion.regeneration = Pembaruan potion.resistance = Daya tahan potion.fireResistance = Daya tahan api potion.waterBreathing = Bernafas dalam air potion.invisibility = Tidak tampak potion.blindness = Kebutaan potion.nightVision = Penglihatan malam potion.hunger = Lapar potion.weakness = Kelemahan potion.poison = Racun potion.healthBoost = Pemulih cepat potion.absorption = Penyerapan potion.saturation = Kejenuhan commands.ban.success = Memblokir pemain {%0} commands.ban.usage = /ban [alasan...] commands.banip.invalid = Alamat IP salah atau pemain tidak online commands.banip.success.players = Alamat IP {%0} diblokir selama {%1} commands.banip.success = Alamat IP {%0} telah diblokir commands.banip.usage = /ban-ip [alasan...] commands.banlist.ips = Terdapat %d alamat IP yang diblokir: commands.banlist.players = Ada {%0} total pemain banned: commands.defaultgamemode.success = Mode permainan dasar dunia sekarang adalah {%0} commands.deop.success = Mengambil status Op dari {%0} commands.deop.usage = /deop commands.difficulty.success = Mengubah kesulitan permainan menjadi {%0} commands.effect.failure.notActive.all = Tidak dapat mengambil efek dari {%0} karena mereka tidak memiliki efek commands.effect.failure.notActive = Tidak dapat mengambil efek {%0} dari {%1} karena mereka tidak memiliki efek commands.effect.notFound = Tidak ada efek mob dengan ID {%0} commands.effect.success.removed.all = Mengambil semua efek dari {%0} commands.effect.success.removed = Mengambil {%0} dari {%1} commands.effect.success = Memberikan {%0} (ID{%1})*{%2} kepada {%3} selama {%4} detik commands.effect.usage = /effect [waktu] ATAU /effect clear commands.enchant.noItem = Target tidak memegang suatu benda commands.enchant.notFound = Tidak ada tingkatan dengan ID {%0} commands.enchant.success = berhasil meningkatkan commands.gamemode.success.other = Mengubah mode permainan {%0} menjadi {%1} commands.gamemode.success.self = Mengubah mode permainan menjadi {%2} commands.gamemode.usage = /gamemode [pemain] commands.generic.exception = Error saat menggunakan perintah ini commands.generic.notFound = Perintah tidak ditemukan. /help untuk daftar perintah commands.generic.permission = Anda tidak memiliki izin untuk menggunakan perintah ini commands.generic.player.notFound = Pemain tersebut tidak ditemukan commands.generic.usage = Penggunaan: {%0} commands.give.item.notFound = Tidak ada benda dengan nama {%0} commands.give.success = Memberikan {%0} sebanyak {%1} untuk {%2} commands.give.tagError = Penguraian tanda tag gagal: {%0} commands.help.header = > Menampilkan daftar bantuan halaman {%0} dari {%1} (/help ) commands.help.usage = /help [halaman|command] commands.kick.success.reason = Mengeluarkan {%0} dari server. Alasan:'{%1}' commands.kick.success = {%0} Dikeluarkan dari server commands.kick.usage = /kick [alasan...] commands.kill.successful = Membunuh {%0} commands.me.usage = /me commands.message.sameTarget = Anda tidak bisa mengirim pesan ke diri sendiri! commands.message.usage = /tell commands.op.success = Menjadikan {%0} sebagai Op commands.op.usage = /op commands.particle.notFound = Nama partikel {%0} tidak diketahui commands.particle.success = Memainkan Partikel {%0} untuk waktu {%1} commands.players.list = Ada {%0}/{%1} pemain online: commands.save.disabled = Mematikan penyimpanan automatis commands.save.enabled = Menyalakan penyimpanan automatis commands.save.start = Menyimpan... commands.save.success = Dunia tersimpan commands.say.usage = /say commands.setworldspawn.success = Menetapkan spawn world di ({%0},{%1},{%2}) commands.setworldspawn.usage = /setworldspawn [] commands.spawnpoint.success = Menetapkan spawn point {%0} ke ({%1},{%2},{%3}) commands.spawnpoint.usage = /spawnpoint [pemain] [] commands.stop.start = Memberhentikan server commands.time.added = Menambahkan {%0} ke waktu commands.time.query = Waktu saat ini {%0} commands.time.set = Menetapkan waktu untuk {%0} commands.tp.success.coordinates = Menteleportasi {%0} ke {%1},{%2},{%3} commands.tp.success = Menteleportasi {%0} ke {%1} commands.tp.usage = /tp [pemain tujuan] ATAU /tp [pemain tujuan] [] commands.unban.success = Mengijinkan pemain {%0} bermain kembali commands.unban.usage = /pardon commands.unbanip.invalid = Kesalahan alamat IP commands.unbanip.success = Mengijinkan alamat IP {%0} bermain kembali commands.unbanip.usage = /pardon-ip commands.whitelist.add.success = Menambahkan {%0} ke dalam whitelist commands.whitelist.add.usage = /whitelist tambah commands.whitelist.disabled = Menonaktifkan whitelist commands.whitelist.enabled = Mengaktifkan whitelist commands.whitelist.list = Ada {%0} (dari sekian {%1} terlihat) whitelist pemain: commands.whitelist.reloaded = Memuat whitelist commands.whitelist.remove.success = Menghapus {%0} dari whitelist commands.whitelist.remove.usage = /whitelist remove ; -------------------- PocketMine language strings, only for console -------------------- pocketmine.data.playerNotFound = Data pemain tidak ditemukan untuk "{%0}", membuat profile baru pocketmine.data.playerCorrupted = Data korup ditemukan untuk "{%0}", membuat profile baru pocketmine.data.playerOld = Data pemain lama ditemukan untuk "{%0}, meningkatkan profile pocketmine.data.saveError = Tidak dapat menyimpan data pemain "{%0}". Alasan:{%1} pocketmine.level.notFound = Level "{%0}" tidak ditemukan pocketmine.level.loadError = Tidak dapat memuat level "{%0}". Alasan:{%1} pocketmine.level.generationError = Tidak dapat membuat level "{%0}. Alasan:{%1} pocketmine.level.tickError = Terjadi kesalahan tick level "{%0}":{%1} pocketmine.level.chunkUnloadError = Kerusakan saat unloading chunk: {%0} pocketmine.level.backgroundGeneration = Spawn terrain untuk level "{%0}" akan berjalan di belakang pocketmine.level.defaultError = Level dasar tidak bisa dimuat pocketmine.level.preparing = Mempersiapkan level "{%0}" pocketmine.server.start = Memulai Minecraft:PE server versi {%0} pocketmine.server.networkError = [Network] Memberhentikan antar muka {%0} disebabkan oleh {%1} pocketmine.server.networkStart = Membuka server dalam {%0}:{%1} pocketmine.server.info = Server ini berjalan dengan {%0} Versi {%1} "{%2}" (API {%3}) pocketmine.server.info.extended = Server ini berjalan dengan versi {%0} {%1} 「{%2} menggunakan versi API {%3} for Minecraft: PE {%4} (protocol version {%5}) pocketmine.server.license = {%0} distribusi dibawah Lisensi LGPL pocketmine.server.tickOverload = Tidak dapat mengikuti! Apakah server terlalu penuh? pocketmine.server.startFinished = Selesai ({%0} detik)! Untuk bantuan, ketik "help" atau "?" pocketmine.server.defaultGameMode = Tipe gamemode dasar: {%0} pocketmine.server.query.start = Menjalankan GS4 status listener pocketmine.server.query.info = Mengatur query port ke {%0} pocketmine.server.query.running = Query berjalan dalam {%0}:{%1} pocketmine.command.exception = Perintah eksepsi tidak tertangani '{%0}' dalam {%1}:{%2} pocketmine.command.plugins.description = Mendapatkan daftar plugin dari server pocketmine.command.reload.description = Memuat ulang konfigurasi dan plugin server pocketmine.command.reload.reloading = Memuat ulang server... pocketmine.command.reload.reloaded = Memuat ulang selesai. pocketmine.command.status.description = Baca kembali performa server. pocketmine.command.gc.description = Kebakaran tugas pengumpulan sampah pocketmine.command.timings.description = Merekam timing untuk melihat performa dari server. pocketmine.command.timings.enable = Mengaktifkan Timing dan Reset pocketmine.command.timings.disable = Menonaktifkan Timing pocketmine.command.timings.timingsDisabled = Mohon aktifkan timing dengan ketik /timings on pocketmine.command.timings.reset = Timing reset pocketmine.command.timings.pasteError = Terjadi kerusakan saat mempastikan laporan pocketmine.command.timings.timingsUpload = Timing di unggah ke {%0} pocketmine.command.timings.timingsRead = Anda bisa membaca hasil di {%0} pocketmine.command.timings.timingsWrite = Timing ditulis ke {%0} pocketmine.command.version.description = Mendapatkan versi server ini berdasarkan semua plugin yang di gunakan pocketmine.command.version.noSuchPlugin = Server ini tidak menjalankan plugin tersebut. Gunakan /plugins untuk mendapat daftar plugin. pocketmine.command.give.description = Memberikan pemain jumlah item pocketmine.command.give.usage = /beri [jumlah] [tag...] pocketmine.command.kill.description = Melakukan bunuh diri atau membunuh pemain lain pocketmine.command.particle.description = Tambahkan partikel ke dalam world pocketmine.command.time.description = Mengubah waktu dalam masing-masing world pocketmine.command.ban.player.description = Mencegah pemain tertentu dari menggunakan server ini pocketmine.command.ban.ip.description = Mencegah beberapa alamat IP dari menggunakan server ini pocketmine.command.banlist.description = Lihat semua pemain banned dari server ini pocketmine.command.defaultgamemode.description = Menetapkan gamemode dasar pocketmine.command.deop.description = Ambil beberapa status operator pemain pocketmine.command.difficulty.description = Menetapkan kesulitan permainan pocketmine.command.enchant.description = Menambahkan penyihiran ke item pocketmine.command.effect.description = Tambahkan/Hilangkan efek dari pemain pocketmine.command.gamemode.description = Mengubah pemain ke gamemode yang ditentukan pocketmine.command.help.description = Lihag daftar bantuan pocketmine.command.kick.description = Hapus beberapa pemain dari server pocketmine.command.list.description = Daftar seluruh pemain online pocketmine.command.me.description = Tampilkan beberapa aksi di obrolan pocketmine.command.op.description = Berikan beberapa status pemain operator pocketmine.command.unban.player.description = Izinkan beberapa pemain untuk menggunakan server ini pocketmine.command.unban.ip.description = Izinkan alamat IP untuk menggunakan server ini pocketmine.command.save.description = Menyimpan server ke penyimpanan pocketmine.command.saveoff.description = Hilangkan menyimpan otomatis pocketmine.command.saveon.description = Mengaktifkan server automatis menyimpan pocketmine.command.say.description = Pengumuman pesan sebagai pengirim pocketmine.command.seed.description = Lihat seed world pocketmine.command.setworldspawn.description = Tetapkan point spawn. Jika tidak ada koordinat yang di pilih, koordinat player akan di gunakan. pocketmine.command.spawnpoint.description = Tetapkan pemain spawn point pocketmine.command.stop.description = memberhentikan server pocketmine.command.tp.description = Teleport pemain yang dituju (diri sendiri) untuk menuju ke pemain lain atau koordinat pocketmine.command.tell.description = Kirim pesan untuk pemain yang dituju pocketmine.command.whitelist.description = Mengatur daftar pemain yang dapat bermain ke server pocketmine.crash.create = Kerusakan tidak teratasi terjadi dan server rusak. Membuat laporan kerusakan pocketmine.crash.error = Tidak dapat membuat laporan kerusakan: {%0} pocketmine.crash.submit = Mohon unggah "{%0}" file ke Laporan Kerusakan dan unggah link ke halaman Laporan Bug. Berikan sebanyak banyaknya info sebisa anda. pocketmine.crash.archive = "Bukti kerusakan automatis di submit ke Laporan Kerusakan. Anda bisa lihat dalam {%0} atau gunakan ID #{%1}." pocketmine.debug.enable = Dukungan LevelDB aktif pocketmine.player.invalidMove = {%0} Bergerak salah arah! pocketmine.player.logIn = {%0}[/{%1}:{%2}] masuk dengan ID {%3} di ({%4}, {%5}, {%6}, {%7}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] Keluar karena {%3} pocketmine.player.invalidEntity = {%0} mencoba menyerang entity yang salah pocketmine.plugin.load = Memuat {%0} pocketmine.plugin.enable = Mengaktifkan {%0} pocketmine.plugin.disable = Menonaktifkan {%0} pocketmine.plugin.restrictedName = Nama terbatas pocketmine.plugin.incompatibleAPI = Versi API tidak kompatibel pocketmine.plugin.unknownDependency = Ketergantungan tidak diketahui pocketmine.plugin.circularDependency = Ketergantungan berkaitan terdeteksi pocketmine.plugin.genericLoadError = Tidak dapar memuat plugin '{%0}' pocketmine.plugin.spacesDiscouraged = Plugin '{%0}' menggunakan spasi dalam namanya, ini tidak diperbolehkan pocketmine.plugin.loadError = Tidak dapat memuat plugin '{%0}':{%1} pocketmine.plugin.duplicateError = Tidak dapat memuat plugin '{%0}': plugins sudah ada pocketmine.plugin.fileError = Tidak dapat memuat '{%0}' dalam folder '{%1}':{%2} pocketmine.plugin.commandError = Tidak dapat memuat perintah {%0} dari plugin {%1} pocketmine.plugin.aliasError = Alternatif tidak ditemukan {%0} untuk plugin {%1} pocketmine.plugin.deprecatedEvent = Plugin '{%0}' telah terdaftar untuk '{%1}' dalam '{%2}'. pocketmine.plugin.eventError = Tidak dapat pass event '{%0}' ke '{%1}':{%2} dalam {%3} ; -------------------- PocketMine setup-wizard strings, only for console -------------------- # Language file compatible with Minecraft: Pocket Edition identifiers # # A message doesn't need to be there to be shown correctly on the client. # Only messages shown in PocketMine itself need to be here language.name = 日本語 language.selected = {%0} ({%1}) を言語に選択しました multiplayer.player.joined = {%0} がゲームに参加しました multiplayer.player.left = {%0} が退出しました chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0}は{%1}の実績を手に入れました disconnectionScreen.notAuthenticated = Xboxでのログインが必要です disconnectionScreen.outdatedClient = クライアントのバージョンが古いです! disconnectionScreen.outdatedServer = サーバーのバージョンが古いです! disconnectionScreen.serverFull = サーバーは満員です! disconnectionScreen.noReason = サーバーから切断されました disconnectionScreen.invalidSkin = 無効なスキンです disconnectionScreen.invalidName = 無効な名前です! death.fell.accident.generic = {%0} は高いところから落ちた death.attack.inFire = {%0}は炎に巻かれてしまった death.attack.onFire = {%0}はこんがりと焼けてしまった death.attack.lava = {%0}は溶岩遊泳を試みた death.attack.inWall = {%0}は壁の中で窒息してしまった death.attack.drown = {%0}は溺れ死んだ death.attack.cactus = {%0}はサボテンに刺されて死んだ death.attack.generic = {%0}は死んでしまった death.attack.explosion = {%0}は爆発に巻き込まれてしまった death.attack.explosion.player = {%0}は{%1}に爆破されてしまった death.attack.magic = {%0}は魔法で殺された death.attack.wither = {%0}は干からびてしまった death.attack.mob = {%0}は{%1}に殺害された death.attack.player = {%0}は{%1}に殺害された death.attack.player.item = {%0}は{%1}の{%2}で殺害された death.attack.arrow = {%0}は{%1}に射抜かれた death.attack.arrow.item = {%0}は{%1}の{%2}で射抜かれた death.attack.fall = {%0}は地面と強く激突してしまった death.attack.outOfWorld = {%0}は奈落の底へ落ちてしまった gameMode.survival = サバイバルモード gameMode.creative = クリエイティブモード gameMode.adventure = アドベンチャーモード gameMode.spectator = スペクテイターモード gameMode.changed = ゲームモードが変更されました potion.moveSpeed = 移動速度上昇 potion.moveSlowdown = 移動速度低下 potion.digSpeed = 採掘速度上昇 potion.digSlowDown = 採掘速度低下 potion.damageBoost = 攻撃力上昇 potion.heal = 即時回復 potion.harm = ダメージ potion.jump = 跳躍力上昇 potion.confusion = 吐き気 potion.regeneration = 再生能力 potion.resistance = 耐性 potion.fireResistance = 火炎耐性 potion.waterBreathing = 水中呼吸 potion.invisibility = 透明化 potion.blindness = 盲目 potion.nightVision = 暗視 potion.hunger = 空腹 potion.weakness = 弱体化 potion.poison = 毒 potion.wither = ウィザー potion.healthBoost = 体力増強 potion.absorption = 衝撃吸収 potion.saturation = 満腹度回復 commands.generic.exception = コマンドの実行中に不明なエラーが発生しました commands.generic.permission = このコマンドを使用する権限がありません commands.generic.notFound = 未知のコマンドです。/helpでコマンドの一覧を確認してください commands.generic.player.notFound = プレイヤーが見つかりません commands.generic.usage = 使い方: {%0} commands.generic.level = level-name commands.generic.seed = seed-name commands.generic.name = name commands.generic.generator = generator-name commands.generic.opt.missing = 必要なオプションが入力されていません。確認して再入力してください。 commands.generic.runingame = コマンドはゲーム内で使用してください。 commands.time.added = 時間を{%0}進めました commands.time.set = 現在時刻を{%0}に設定しました commands.time.query = 現在の時間は {%0} です commands.me.usage = /me <アクション> commands.give.item.notFound = {%0}という名前のアイテムはありません commands.give.success = {%0} を {%1} 個 {%2} に与えました commands.give.tagError = データタグの解析に失敗しました: {%0} commands.effect.usage = /effect <プレイヤー名> <効果> [秒数] [倍数] [パーティクルの設定] か /effect <プレイヤー名> clear commands.effect.notFound = ID{%0}のエフェクトは存在しません commands.effect.success = {%3}に{%0} (ID {%1})×{%2}を{%4}秒間与えました commands.effect.success.removed = {%0}を{%1}から除去しました commands.effect.success.removed.all = {%0}からすべてのエフェクトを除去しました commands.effect.failure.notActive = {%1} は {%0} というエフェクトは付与されてないので除去することができませんでした commands.effect.failure.notActive.all = {%0}はステータス効果を受けていないので除去することはできませんでした commands.enchant.noItem = 対象のプレイヤーはアイテムを持っていません commands.enchant.notFound = ID {%0} に該当するエンチャントはありません commands.enchant.success = エンチャントに成功しました commands.enchant.cantEnchant = このアイテムはエンチャントをすることができません commands.enchant.usage = /enchant <プレイヤー名> <エンチャントID> [レベル] commands.particle.success = 効果{%0}を{%1}回発生させます commands.particle.notFound = 存在しないエフェクト:{%0} commands.players.usage = /list commands.players.list = 現在{%0}人(最大{%1}人)がオンライン: commands.kill.successful = {%0}を殺しました commands.banlist.ips = %d 個のIPアドレスがBANされています: commands.banlist.players = {%0} 人のプレイヤーがBANされています: commands.banlist.cids = {%0} 個のCIDがBANされています: commands.banlist.usage = /banlist [ips|players|cids] commands.defaultgamemode.usage = /defaultgamemode <モード> commands.defaultgamemode.success = ワールドのデフォルトのゲームモードを {%0} にしました。 commands.op.success = {%0} にオペレーター権限を付与しました commands.op.usage = /op <プレイヤー名> commands.deop.success = {%0} からオペレーター権限を剥奪しました commands.deop.usage = /deop <プレイヤー名> commands.say.usage = /say <メッセージ ...> commands.seed.usage = /seed commands.seed.success = Seed値:{%0} commands.bancidbyname.success = プレイヤー {%0}のCIDはBANされました。 commands.bancidbyname.usage = /bancidbyname commands.bancid.success = CID: {%0}はBANされました commands.bancid.usage = /bancid commands.unbancid.usage = /pardoncid commands.unbancid.success = CID {%0} のBANが解除されました commands.ban.success = {%0} をBANしました commands.ban.usage = /ban <プレイヤー名> [理由 ...] [時間(日)] commands.unban.success = {%0} のbanを解除しました commands.unban.usage = /pardon <プレイヤー名> commands.banip.invalid = 無効なIPアドレスが入力されたか、プレイヤーがオンラインになっていません commands.banip.success = IPアドレス "{%0}" をBANしました。 commands.banip.success.players = {%1} のIPアドレス {%0} をBANしました commands.banip.usage = /ban-ip [理由] commands.unbanip.invalid = 無効なIPアドレスです commands.unbanip.success = IPアドレス {%0} のBANが解除されました commands.unbanip.usage = /pardon-ip commands.banipbyname.success = プレイヤー {%0}の IPはBANされました。 commands.banipbyname.usage = /banipbyname <プレイヤー> commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = ワールドの自動保存を有効にしました commands.save.disabled = ワールドの自動保存を無効にしました commands.save.start = 保存中… commands.save.success = ワールドを保存しました commands.setblock.usage = /setblock <ブロック名> [データ値] command.fill.invalidBlock = 無効なブロック名/ID commands.stop.usage = /stop commands.stop.start = サーバーを停止しています commands.kick.success = {%0}さんはkickされてゲームから切断しました。 commands.kick.success.reason = {%0} を {%1} サーバーからキックした commands.kick.usage = /kick <プレイヤー名> [理由...] commands.tp.success = {%0} から {%1} へワープしました commands.tp.success.coordinates = {%0} は {%1}, {%2}, {%3} にテレポートしました commands.tp.usage = /tp [対象のプレイヤー] <移動先のプレイヤー> または /tp [対象のプレイヤー] [ のみ] commands.whitelist.list = ホワイトリストには {%0} 人 (サーバー全体では {%1} 人) のプレイヤーがいます commands.whitelist.enabled = ホワイトリストを有効にしました commands.whitelist.disabled = ホワイトリストを無効にしました commands.whitelist.reloaded = ホワイトリストを再読み込みしました commands.whitelist.add.success = {%0} をホワイトリストに追加しました commands.whitelist.add.usage = /whitelist add <プレイヤー名> commands.whitelist.remove.success = {%0} をホワイトリストから削除しました commands.whitelist.remove.usage = /whitelist remove <プレイヤー名> commands.whitelist.usage = /whitelist commands.gamemode.success.self = ゲームモードを{%2}に変更しました commands.gamemode.success.other = {%0}のゲームモードを{%1}に変更しました commands.gamemode.usage = /gamemode <モード> [プレイヤー名] commands.help.header = --- ヘルプページの {%0} / {%1} ページを表示(/help <ページ番号>) --- commands.help.usage = /help [ページ|コマンド名] commands.message.usage = /tell <プレイヤー名> <プライベートメッセージ> commands.message.sameTarget = 自分自身にプライベートメッセージを送信することはできません! commands.xp.usage = /xp <経験値またはレベル+L> <プレイヤー名> commands.difficulty.usage = /difficulty <難易度> commands.difficulty.success = ゲームの難易度を{%0}に設定しました commands.spawnpoint.usage = /spawnpoint [プレイヤー名] [] commands.spawnpoint.success = {%0} のスポーン地点を ({%1}, {%2}, {%3}) に変更しました commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = ({%0},{%1},{%2}) にワールドのスポーンポイントを設定します commands.summon.usage = /summon [エンティティ名] [ ] [データタグ] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = {%0} のプレイヤーデータが見つからないため、新たに作成します pocketmine.data.playerCorrupted = {%0} のデータの破損が見つかったため、新たに作成します pocketmine.data.playerOld = {%0} の古いプレイヤーデータが見つかったため、更新します pocketmine.data.saveError = {%0}のデータを保存できませんでした: {%1} pocketmine.level.notFound = ワールド "{%0}" が見つかりません pocketmine.level.loadError = ワールド "{%0}" を読み込むことができませんでした: {%1} pocketmine.level.generationError = ワールド"{%0}"を生成することができませんでした: {%1} pocketmine.level.tickError = ワールド "{%0}" をチェックすることができませんでした: {%1} pocketmine.level.chunkUnloadError = チャンクの書き込み中にエラーが発生しました: {%0} pocketmine.level.backgroundGeneration = ワールド"{%0}"の地形をバックグラウンドで生成しています pocketmine.level.defaultError = デフォルトのワールドが読み込まれていません pocketmine.level.preparing = ワールド "{%0}" を読み込んでいます pocketmine.level.unloading = ワールド"{%0}"の書き込みをしています pocketmine.server.start = Minecraft: PEサーバー({%0}に対応)を起動しています pocketmine.server.networkError = [Network] {%1}によって{%0}のインターフェイスが停止しました pocketmine.server.networkStart = {%0}:{%1}上でサーバーを開始しています pocketmine.server.info = このサーバーは{%0}のバージョン{%1}「{%2}」(API {%3})で動作しています pocketmine.server.info.extended.title = -----サーバーの情報----- pocketmine.server.info.extended1 = このサーバーは {%0}{%1} ({%2}) を実行しています (コードネーム "{%3}") pocketmine.server.info.extended2 = PHPバージョン: {%0} pocketmine.server.info.extended3 = API: {%0} pocketmine.server.info.extended4 = 目的のクライアント: Minecraft PE {%0} (プロトコルバージョン {%1}) pocketmine.server.license = {%0}はLGPLライセンスに基づき配布されています pocketmine.server.tickOverload = 注意! サーバーが高負荷になっている可能性があります pocketmine.server.startFinished = 起動完了({%0}秒)! "help"または"?"でヘルプを表示 pocketmine.server.defaultGameMode = デフォルトゲームタイプ: {%0} pocketmine.server.query.start = GS4ステータス リスナーを開始 pocketmine.server.query.info = クエリポートを設定: {%0} pocketmine.server.query.running = クエリーは {%0}:{%1} で動作しています pocketmine.command.alias.illegal = 無効な文字が含まれているため、{%0}を登録できませんでした pocketmine.command.alias.notFound = 存在しないコマンドが含まれているため、{%0}を登録できませんでした: {%1} pocketmine.command.exception = コマンド'{%0}'を{%1}で実行中に、処理できない例外が発生:{%2} pocketmine.commands.cave.usage = /cave <回転角> <長さ> <分岐数> <強さ> <ワールド名> | /cave getmypos pocketmine.commands.cave.info = 回転角:{%0} 長さ:{%1} 分岐数:{%2} 強さ:{%3} pocketmine.commands.cave.start = 洞窟を生成しています。しばらくお待ちください pocketmine.commands.cave.success = 洞窟が生成されました pocketmine.command.plugins.description = サーバー上で実行されているプラグインの一覧を表示します pocketmine.command.plugins.success = プラグイン ({%0}): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = サーバーの設定やプラグインを再読み込みします pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = サーバーを再読み込みしています... pocketmine.command.reload.reloaded = 再読み込みが完了しました pocketmine.command.lvdat.description = マップの設定を変更します。 pocketmine.command.lvdat.changed = ワールド "{%0}" の {%1} が変更されました。 幾つかの変更はサーバーを再起動する必要があります。 pocketmine.command.lvdat.fixname = "{%0}" の fixname が成功しました。 幾つかの変更はサーバーを再起動する必要があります。 pocketmine.command.lvdat.nofound = level "{%0}" は存在しないかロードに失敗しています。 pocketmine.command.lvdat.preset = ジェネレーターの設定 (プリセット) pocketmine.command.status.description = サーバーのパフォーマンスを読み出します pocketmine.command.status.usage = /status pocketmine.command.status.title = サーバーステータス pocketmine.command.status.player = プレイヤー人数: pocketmine.command.status.days = 日数 pocketmine.command.status.hours = 時間 pocketmine.command.status.minutes = 分 pocketmine.command.status.seconds = 秒 pocketmine.command.status.uptime = 起動時間: pocketmine.command.status.AverageTPS = 平均のTPS: pocketmine.command.status.CurrentTPS = 現在のTPS: pocketmine.command.status.Networkupload = ネットワークアップロード: pocketmine.command.status.Networkdownload = ネットワークダウンロード: pocketmine.command.status.Threadcount = スレッド数: pocketmine.command.status.Mainmemory = メインスレッド数: pocketmine.command.status.Totalmemory = 合計のメモリ使用量: pocketmine.command.status.Totalvirtualmemory = 合計の仮想メモリ使用量: pocketmine.command.status.Heapmemory = ヒープ領域: pocketmine.command.status.Maxmemorysystem = 最大メモリ (システム): pocketmine.command.status.Maxmemorymanager = 最大メモリ (マネージャー): pocketmine.command.status.World = ワールド pocketmine.command.status.chunks = チャンク, pocketmine.command.status.entities = エンティティ, pocketmine.command.status.tiles = タイル. pocketmine.command.status.Time = 時間 pocketmine.command.status.ms = ms pocketmine.command.gc.description = ガベージコレクションのタスクを起動 pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = コレクションレポート pocketmine.command.gc.chunks = チャンク: pocketmine.command.gc.entities = エンティティ: pocketmine.command.gc.tiles = タイル: pocketmine.command.gc.cycles = サイクル: pocketmine.command.gc.memory = 解放されたメモリ: pocketmine.command.biome.description = エリアのバイオームを変更します。(雪や雨を変更できます) pocketmine.command.biome.posset = ポジション {%3} を ({%1},{%2}) ワールド {%0} にセットしました。 pocketmine.command.biome.get = あなたが立っている場所のバイオームIDは {%0} です。 色:{%1},{%2},{%3} pocketmine.command.biome.wrongLev = ワールドをまたいでポジションをセットすることはできません。 pocketmine.command.biome.wrongBio = バイオームIDが間違っています。 例: 1 (草原), 2 (砂漠),13 (雪山),6 (湿地) pocketmine.command.biome.wrongCol = 色が間違っています。 例: 146,188,89 "/biome get" で他の色を取得できます。 pocketmine.command.biome.noPos = 最初に "/biome pos1|pos2" を使ってエリアを指定してください。 pocketmine.command.biome.set = 選択された場所のバイオームを {%0} に設定しました。 pocketmine.command.biome.color = 選択された場所の草ブロックの色を {%0},{%1},{%2} に設定しました。 pocketmine.command.timings.description = サーバーのパフォーマンスを確認する記録のタイミングを設定します pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = タイミングをリセット及び有効にしました pocketmine.command.timings.disable = タイミングを無効にしました pocketmine.command.timings.timingsDisabled = /timings と入力してタイミングを有効にしてください pocketmine.command.timings.reset = タイミングをリセットしました pocketmine.command.timings.pasteError = レポートのペースト中にエラーが発生しました pocketmine.command.timings.timingsUpload = タイミングを{%0}にアップロードします pocketmine.command.timings.timingsRead = 結果は {%0} で見ることができます pocketmine.command.timings.timingsWrite = タイミングを {%0} に書き込んでいます pocketmine.command.version.description = 使用しているプラグインとこのサーバーのバージョンを取得します pocketmine.command.version.usage = /version [プラグイン名] pocketmine.command.version.noSuchPlugin = このサーバーではその名前のプラグインは実行していません プラグインリストを取得するには /plugins を使用してください pocketmine.command.give.description = 指定したプレイヤーに一定量のアイテムを付与します pocketmine.command.give.usage = /give <プレイヤー名> <アイテム[:ダメージ値]> [量] [タグ] pocketmine.command.kill.description = 自殺または他のプレイヤーを殺すことができます pocketmine.command.kill.usage = /kill [プレイヤー名] pocketmine.command.particle.description = ワールドにパーティクルを追加します pocketmine.command.particle.usage = /particle <パーティクル名> [量] [データ値] pocketmine.command.time.description = それぞれのワールドの時間を変更します pocketmine.command.time.usage = /time <値> または /time pocketmine.command.bancidbyname.description = 指定したCIDをプレイヤー名によってサーバーへの接続をBanします pocketmine.command.bancid.description = 指定したCIDからのサーバーへの接続をBanします pocketmine.command.banipbyname.description = 指定したIPアドレスをプレイヤー名によってサーバーへの接続をBanします pocketmine.command.ban.player.description = 指定したプレーヤーのサーバーへの接続をBanします pocketmine.command.ban.ip.description = 指定したIPアドレスからのサーバーへの接続をBanします pocketmine.command.banlist.description = このサーバーでBANされたすべてのプレイヤーを表示します pocketmine.command.defaultgamemode.description = デフォルトのゲームモードを設定します pocketmine.command.deop.description = 指定プレイヤーのOP権限を剥奪します pocketmine.command.difficulty.description = ゲーム難易度を設定します pocketmine.command.enchant.description = アイテムにエンチャントを付加します pocketmine.command.effect.description = プレイヤーにエフェクトを付与/削除します pocketmine.command.gamemode.description = プレイヤーのゲームモードを変更します pocketmine.command.help.description = ヘルプメニューを表示します pocketmine.command.kick.description = 指定したプレイヤーをサーバーから追い出します pocketmine.command.list.description = サーバーに接続しているプレイヤーの一覧を表示します pocketmine.command.me.description = チャットで指定したアクションを実行します pocketmine.command.op.description = 指定したプレイヤーにOP権限を付与します pocketmine.command.unban.cid.description = 指定したCIDのBANを解除します pocketmine.command.unban.player.description = 指定したプレイヤーのBANを解除します pocketmine.command.unban.ip.description = 指定したIPアドレスのBANを解除します pocketmine.command.save.description = サーバーを保存します pocketmine.command.saveoff.description = サーバーの自動保存を無効にします pocketmine.command.saveon.description = サーバーの自動保存を有効にします pocketmine.command.say.description = 送信者として指定したメッセージを送信します pocketmine.command.seed.description = ワールドのシード値を表示します pocketmine.command.setworldspawn.description = ワールドのスポーン地点を設定します 座標が指定されていない場合はプレイヤーの現在地の座標が使用されます pocketmine.command.spawnpoint.description = プレイヤーのスポーン地点を設定します pocketmine.command.stop.description = サーバーを停止します pocketmine.command.tp.description = 他のプレイヤーまたは指定した座標にプレイヤー(または自分自身)をテレポートさせます pocketmine.command.tell.description = 指定したプレイヤーにプライベートメッセージを送信します pocketmine.command.xp.description = 指定プレイヤーの経験値またはレベルを増加させます pocketmine.command.summon.description = プレーヤーの場所または特定の場所での実体を召喚 pocketmine.command.fill.description = 指定した範囲を、指定したブロックIDに置換ます。 pocketmine.command.setblock.description = 指定した座標にブロックを配置します。 pocketmine.command.weather.description = ワールドの天気をセットします pocketmine.command.weather.usage = /weather pocketmine.command.weather.changed = ワールド {%0} で天気の変更に成功しました! pocketmine.command.weather.noregistered = ワールド {%0} はWeatherManagerに登録されていません pocketmine.command.weather.invalid = 不正な天気。(0,1,2,3) pocketmine.command.weather.wrong = 間違ったパラメーター. pocketmine.command.weather.invalid.level = 不正なワールド名。 pocketmine.command.whitelist.description = このサーバーの利用を許可するプレイヤーリストを管理します pocketmine.crash.create = リカバリー不可能なエラーが発生し、サーバーがクラッシュ(動作を停止)しました。クラッシュダンプを作成しました pocketmine.crash.error = クラッシュダンプの作成に失敗: {%0} pocketmine.crash.submit = 「{%0}」のファイルをクラッシュアーカイブにアップロードし、そのリンクを送ってください 出来る限り多くの情報を記載してください pocketmine.crash.archive = クラッシュダンプが自動的にクラッシュアーカイブに提出されました {%0}で閲覧またはID #{%1}を使用可能です pocketmine.debug.enable = LevelDB形式の対応を有効にしました pocketmine.player.invalidMove = {%0} が正しく移動できませんでした! pocketmine.player.logIn = {%0}[IP:§a{%1}§f | Port:{%2}] [ClientID: {%3}] が EntityId {%4} としてログインしました [座標 ({%5}, {%6}, {%7}, {%8})] pocketmine.player.logOut = {%0}[IP:§a{%1}§f | Port:{%2}] が {%3} でログアウトしました pocketmine.player.transferred = {%0}[IP:{%1} | Port:{%2}] は {%3}に転送されました。 pocketmine.player.invalidEntity = {%0} は無効なエンティティに攻撃しました pocketmine.plugin.load = {%0} を読み込み中... pocketmine.plugin.enable = {%0}を有効にしています… pocketmine.plugin.disable = {%0}を無効にしています… pocketmine.plugin.restrictedName = 不審な名前 pocketmine.plugin.incompatibleAPI = 互換性がないAPIバージョン pocketmine.plugin.unknownDependency = 不明な依存関係 pocketmine.plugin.circularDependency = 循環依存が検出されました pocketmine.plugin.genericLoadError = プラグイン'{%0}'の読み込みに失敗しました pocketmine.plugin.spacesDiscouraged = プラグイン'{%0}'の名前に空白が使用されていますが、これはおすすめしません pocketmine.plugin.loadError = プラグイン'{%0}'を読み込むことができませんでした: {%1} pocketmine.plugin.duplicateError = プラグイン'{%0}'を読み込むことができませんでした: 同じプラグインが存在します pocketmine.plugin.fileError = フォルダー'{%0}'内の'{%1}'を読み込むことができませんでした: {%2} pocketmine.plugin.commandError = プラグイン{%1}のコマンド{%0}を読み込むことができませんでした pocketmine.plugin.aliasError = プラグイン {%1} で、コマンド{%0} に対するエイリアスを登録できませんでした pocketmine.plugin.deprecatedEvent = プラグイン'{%0}'がメソッド'{%2}'に'{%1}'のリスナーを登録しましたが、イベントが廃止されました pocketmine.plugin.eventError = "'{%1}'に'{%0}'Eventを渡すことができませんでした: {%3}での{%2}" # Language file compatible with Minecraft: Pocket Edition identifiers # # A message doesn't need to be there to be shown correctly on the client. # Only messages shown in PocketMine itself need to be here language.name = "한국어 | Johnmacrocraft/엔로그가 번역" language.selected = {%0} ({%1})이(가) 기본 언어로 설정되었습니다. multiplayer.player.joined = {%0}님이 게임에 접속했습니다 multiplayer.player.left = {%0}님이 게임에서 나갔습니다 chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0}님이 도전 과제 {%1}을(를) 달성했습니다 disconnectionScreen.notAuthenticated = Xbox 로그인이 필요합니다 disconnectionScreen.outdatedClient = 오래된 클라이언트입니다! disconnectionScreen.outdatedServer = 오래된 서버입니다! disconnectionScreen.serverFull = 서버 최대 인원수를 초과하였습니다. disconnectionScreen.noReason = 서버와의 연결이 끊어졌습니다. disconnectionScreen.invalidSkin = 올바르지 않거나 지원되지 않는 스킨입니다! disconnectionScreen.invalidName = 올바르지 않거나 잘못된 형식의 이름입니다! disconnectionScreen.refusedResourcePack = 리소스팩 적용은 필수 사항입니다! disconnectionScreen.unavailableResourcePack = 서버에서 리소스팩을 사용할 수 없습니다! death.fell.accident.generic = {%0}님이 높은 곳에서 떨어졌습니다 death.attack.inFire = {%0}님이 불에 타 죽었습니다 death.attack.onFire = {%0}님이 불에 타서 죽었습니다 death.attack.lava = {%0}님이 용암에 빠져 죽었습니다 death.attack.inWall = {%0}님이 벽에 끼어 질식사했습니다 death.attack.drown = {%0}님이 익사했습니다 death.attack.cactus = {%0}님이 가시에 찔려서 사망했습니다 death.attack.generic = {%0}님이 사망했습니다 death.attack.explosion = {%0}님이 폭발로 인해 사망하셨습니다. death.attack.explosion.player = {%0}님이 {%1}님에 의해 폭사했습니다 death.attack.magic = {%0}님이 마법에 의해 죽었습니다 death.attack.wither = {%0}님이 말라서 죽었습니다 death.attack.mob = {%0}님이 {%1} 에게 살해당했습니다 death.attack.player = {%0}님이 {%1} 에게 살해당했습니다 death.attack.player.item = {%0}님이 {%1}님에게 {%2}(으)로 살해당했습니다 death.attack.arrow = {%0}님이 {%1} 님에게 저격당했습니다 death.attack.arrow.item = {%0}님이 {%1}님에게 {%2}(으)로 저격당했습니다. death.attack.fall = {%0}님이 너무 높은 곳에서 떨어져 사망했습니다 death.attack.outOfWorld = {%0}님이 세계 밖으로 떨어져서 사망했습니다 gameMode.survival = 서바이벌 모드 gameMode.creative = 크리에이티브 모드 gameMode.adventure = 모험 모드 gameMode.spectator = 관전 모드 gameMode.changed = 게임 모드가 변경되었습니다 potion.moveSpeed = 신속 potion.moveSlowdown = 구속 potion.digSpeed = 성급함 potion.digSlowDown = 피로 potion.damageBoost = 힘 potion.heal = 즉시 회복 potion.harm = 즉시 피해 potion.jump = 점프 강화 potion.confusion = 멀미 potion.regeneration = 재생 potion.resistance = 저항 potion.fireResistance = 화염 저항 potion.waterBreathing = 수중 호흡 potion.invisibility = 투명화 potion.blindness = 실명 potion.nightVision = 야간 투시 potion.hunger = 허기 potion.weakness = 나약함 potion.poison = 독 potion.wither = 위더 potion.healthBoost = 체력 강화 potion.absorption = 흡수 potion.saturation = 포화 commands.generic.exception = 이 명령을 수행하는 동안 알 수 없는 오류가 발생했습니다 commands.generic.permission = 이 명령을 사용할 수 있는 권한이 없습니다 commands.generic.notFound = 알 수 없는 명령입니다. /help로 명령어 목록을 보세요 commands.generic.player.notFound = 플레이어를 찾을 수 없습니다 commands.generic.usage = 사용법: {%0} commands.generic.level = 레벨 이름 commands.generic.seed = 시드 이름 commands.generic.name = 이름 commands.generic.generator = 생성기 이름 commands.generic.opt.missing = 필요한 속성이 없습니다. 확인 후 다시 입력해 주세요. commands.generic.runingame = 게임 안에서 이 명령어를 사용해 주세요. commands.time.added = 시간을 {%0}만큼 추가하였습니다 commands.time.set = {%0}(으)로 시간을 설정하였습니다 commands.time.query = 현재 시간은 {%0}입니다 commands.me.usage = /me <행동> commands.give.item.notFound = {%0}의 이름을 가진 아이템이 없습니다 commands.give.success = {%2}님에게 {%0}을(를) {%1}개 주었습니다 commands.give.tagError = 데이터 태그 분석 실패: {%0} commands.effect.usage = /effect <유저명> <효과명> [초] [증폭] [입자 숨김] 또는 /effect <유저명> clear (효과 없애기) commands.effect.notFound = 효과 ID {%0}은(는) 존재하지 않습니다 commands.effect.success = 효과 {%0} (ID {%1} * {%2})를 {%3}에게 {%4}초간 주었습니다 commands.effect.success.removed = {%1}님에게서 효과 {%0}을(를) 제거하였습니다. commands.effect.success.removed.all = {%0} 님의 모든 효과를 제거하였습니다. commands.effect.failure.notActive = {%1}님에게 {%0} 효과를 뺏을 수 없습니다, 어떤 효과도 걸려있지 않습니다. commands.effect.failure.notActive.all = {%0}님에게 어떠한 효과도 걸려있지 않기 때문에 효과를 뺏을 수 없습니다 commands.enchant.maxLevel = 이 마법 부여의 최대 레벨 범위는 1 - {%0}입니다 commands.enchant.noItem = 대상이 아이템을 들고 있지 않아 마법을 부여 할 수 없습니다 commands.enchant.notFound = ID가 {%0}인 마법 부여 효과는 없습니다 commands.enchant.success = 마법 부여를 완료하였습니다 commands.enchant.cantEnchant = 선택한 마법 부여는 대상 아이템에 추가될 수 없습니다 commands.enchant.usage = /enchant <플레이어> <마법 부여 ID> [레벨] commands.particle.success = {%0} 효과를 {%1} 초간 지속합니다 commands.particle.notFound = {%0}은(는) 알 수 없는 효과명입니다 commands.players.usage = /list commands.players.list = 현재 {%0}/{%1}명이 접속 중입니다: commands.kill.successful = {%0}님을 죽였습니다 commands.banlist.ips = 총 %d개의 차단된 IP 주소들이 있습니다: commands.banlist.players = 총 {%0}명의 차단된 플레이어가 있습니다: commands.banlist.cids = 총 {%0}개의 차단된 CID들이 있습니다: commands.banlist.usage = /banlist [아이피|플레이어 이름] commands.defaultgamemode.usage = /defaultgamemode <게임 모드> commands.defaultgamemode.success = 기본 게임 모드가 {%0}(으)로 변경되었습니다. commands.op.success = {%0}을(를) OP로 설정하였습니다 commands.op.usage = /op <플레이어> commands.deop.success = {%0}님의 OP 권한을 박탈하였습니다 commands.deop.usage = /deop <플레이어> commands.say.usage = /say <메시지 ...> commands.seed.usage = /seed commands.seed.success = 시드: {%0} commands.bancidbyname.success = 플레이어 {%0}님의 CID를 차단하였습니다 commands.bancidbyname.usage = /bancidbyname <플레이어 이름> commands.bancid.success = CID {%0}을(를) 차단하였습니다 commands.bancid.usage = /bancid <클라이언트 ID> commands.unbancid.usage = /pardoncid <클라이언트 ID> commands.unbancid.success = CID {%0}의 차단을 해제하였습니다 commands.ban.success = 플레이어 {%0}를 차단하였습니다 commands.ban.usage = /ban <이름> [사유 ...] commands.unban.success = 플레이어 {%0}님을 차단 해제하였습니다 commands.unban.usage = /pardon <차단 해제할 플레이어 이름> commands.banip.invalid = 잘못된 IP 주소이거나 현재 접속해 있지 않은 플레이어입니다 commands.banip.success = IP 주소 {%0}을(를) 차단하였습니다 commands.banip.success.players = {%1}님의 IP 주소 {%0}을(를) 차단하였습니다 commands.banip.usage = /ban-ip [사유 ...] commands.unbanip.invalid = 잘못된 IP 주소를 입력하였습니다 commands.unbanip.success = IP 주소 {%0}의 차단을 해제하였습니다 commands.unbanip.usage = /pardon-ip commands.banipbyname.success = 플레이어 {%0}님의 IP를 차단하였습니다 commands.banipbyname.usage = /banipbyname <플레이어 이름> commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = 세계 자동 저장 기능을 활성화하였습니다 commands.save.disabled = 세계 자동 저장 기능을 비활성화하였습니다 commands.save.start = 세계를 저장 중... commands.save.success = 세계를 저장했습니다 commands.setblock.usage = /setblock <블록 이름> [손상 정도] command.setblock.invalidBlock = 잘못된 블록 이름/ID입니다 commands.stop.usage = /stop commands.stop.start = 서버를 끄는 중입니다 commands.kick.success = {%0}을(를) 게임에서 강제 퇴장시켰습니다 commands.kick.success.reason = {%0}님이 강제 퇴장당하셨습니다. (사유: {%1}) commands.kick.usage = /kick <플레이어> [사유 ...] commands.tp.success = {%0}을(를) {%1}(으)로 이동시켰습니다 commands.tp.success.coordinates = {%0}님을 X:{%1}, Y:{%2}, Z:{%3}(으)로 이동시켰습니다 commands.tp.usage = /tp [이동시킬 플레이어] <도착 지점 플레이어> 또는 /tp [이동시킬 플레이어] [ ] commands.whitelist.list = (보여진 {%1} 중) {%0}명의 화이트리스트 된 플레이어가 있습니다: commands.whitelist.enabled = 화이트리스트 기능을 활성화하였습니다 commands.whitelist.disabled = 화이트리스트 기능을 비활성화하였습니다 commands.whitelist.reloaded = 화이트리스트를 재적재하였습니다. commands.whitelist.add.success = {%0}님을 화이트리스트에 추가하였습니다 commands.whitelist.add.usage = /whitelist add <플레이어> commands.whitelist.remove.success = {%0}님을 화이트리스트에서 삭제하였습니다 commands.whitelist.remove.usage = /whitelist remove <플레이어명> commands.whitelist.usage = /whitelist commands.gamemode.success.self = 자신의 게임 모드를 {%2}(으)로 설정하였습니다 commands.gamemode.success.other = {%0}님의 게임 모드를 {%1}(으)로 설정했습니다 commands.gamemode.usage = /gamemode <모드> [플레이어명] commands.help.header = --- 전체 페이지 {%1} 중 {%0}번째 페이지 (/help <페이지>) --- commands.help.usage = /help [페이지|명령어] commands.message.usage = /tell <플레이어> <비밀 메시지 ...> commands.message.sameTarget = 자기 자신에게 비밀 메시지를 보낼 수 없습니다! commands.difficulty.usage = /difficulty <새로운 난이도> commands.difficulty.success = 게임 난이도를 {%0}(으)로 설정하였습니다 commands.spawnpoint.usage = /spawnpoint [플레이어] [ ] commands.spawnpoint.success = {%0}의 스폰 위치를 ({%1}, {%2}, {%3})로 설정하였습니다 commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = 월드의 스폰 지점을 ({%0}, {%1}, {%2})로 설정하였습니다 commands.summon.usage = /summon [엔티티] [ ] [데이터 태그...] commands.xp.failure.withdrawXp = 0보다 작은 경험치는 줄 수 없습니다 commands.xp.success = %s에게 경험치 %s을(를) 주었습니다 commands.xp.success.levels = %s에게 %s 레벨이 지급되었습니다 commands.xp.success.negative.levels = %s의 레벨을 %s 낮추었습니다 commands.xp.usage = /xp <수량> [플레이어] 또는 /xp <수량>L [플레이어] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = 플레이어 "{%0}"님의 데이터를 찾지 못했습니다. 새 프로필을 생성합니다. pocketmine.data.playerCorrupted = 플레이어 "{%0}"님의 데이터가 손상되었습니다. 새로운 프로필을 생성합니다. pocketmine.data.playerOld = 플레이어 "{%0}"님의 데이터가 오래되었습니다. 프로필을 업데이트합니다. pocketmine.data.saveError = 플레이어 "{%0}"님의 데이터를 저장하지 못했습니다: {%1} pocketmine.level.notFound = "{%0}" 세계를 찾지 못했습니다 pocketmine.level.loadError = 세계 "{%0}"을(를) 불러오지 못했습니다: {%1} pocketmine.level.generationError = 세계 "{%0}"을(를) 생성하지 못했습니다: {%1} pocketmine.level.tickError = "{%0}" 세계의 틱을 처리하지 못했습니다: {%1} pocketmine.level.chunkUnloadError = 청크 언로드 중 오류가 발생했습니다: {%0} pocketmine.level.backgroundGeneration = 세계 "{%0}"의 스폰 지형이 백그라운드에서 생성됩니다... pocketmine.level.defaultError = 기본 세계를 찾을 수 없습니다 pocketmine.level.preparing = 세계 "{%0}"을(를) 준비 중입니다... pocketmine.level.unloading = 세계 "{%0}"을(를) 언로드 중입니다... pocketmine.server.start = 마인크래프트: 포켓 에디션 (버전 {%0}용) 서버를 시작 중입니다... pocketmine.server.networkError = [네트워크] 인터페이스 {%0}이(가) {%1}(으)로 인해 작동 중단되었습니다. pocketmine.server.networkStart = 서버를 {%0}:{%1}에서 여는 중입니다 pocketmine.server.info = 이 서버는 {%0} (버전 {%1} "{%2}" (API {%3}))을(를) 구동 중입니다 pocketmine.server.info.extended.title = -----서버 정보----- pocketmine.server.info.extended1 = 이 서버는 {%0}{%1} ({%2}) (코드네임 "{%3}")을 구동 중입니다 pocketmine.server.info.extended2 = PHP 버전: {%0} pocketmine.server.info.extended3 = API: {%0} pocketmine.server.info.extended4 = 대상 클라이언트: Minecraft PE {%0} (프로토콜 버전 {%1}) pocketmine.server.license = {%0}은(는) LGPL 라이센스 하에서 배포됩니다 pocketmine.server.tickOverload = 서버 처리 속도가 느려졌습니다. 서버가 과부하되었는지 확인해주시기 바랍니다. pocketmine.server.startFinished = 서버 구동 준비 완료! :D ({%0}초) 명령어 목록을 확인하시려면 "help" 또는 "?"를 입력해 주세요. pocketmine.server.defaultGameMode = 기본 게임 모드: {%0} pocketmine.server.query.start = GS4 쿼리 리스너를 시작 중입니다 pocketmine.server.query.info = 쿼리 포트를 {%0}(으)로 설정합니다 pocketmine.server.query.running = 쿼리 서버가 {%0}:{%1}에서 실행 중입니다 pocketmine.command.alias.illegal = 명령어 별칭 {%0}을(를) 등록하지 못했습니다. 잘못된 문자를 포함하고 있습니다. pocketmine.command.alias.notFound = 명령어 별칭 {%0}을(를) 등록하지 못했습니다. 존재하지 않는 명령어를 포함합니다: {%1} pocketmine.command.exception = 플러그인 {%1}에서 명령어 '{%0}'을(를) 실행하는 중 예외가 발생했습니다: {%2} pocketmine.commands.cave.usage = /cave [<회전 각도> <길이> <지점 번호> <강도> <레벨 이름>] | /cave getmypos pocketmine.commands.cave.info = 회전 각도:{%0} 길이:{%1} 지점 번호:{%2} 강도:{%3} pocketmine.commands.cave.start = 동굴을 생성하는 중입니다. 잠시만 기다리세요 pocketmine.commands.cave.success = 동굴이 생성되었습니다 pocketmine.command.plugins.description = 서버에서 실행 중인 플러그인 목록을 불러옵니다. pocketmine.command.plugins.success = 플러그인 목록 (전체 {%0}개): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = 서버 설정과 플러그인을 다시 불러옵니다. pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = 서버를 다시 불러오는 중입니다... pocketmine.command.reload.reloaded = 서버 설정을 다시 불러왔습니다. pocketmine.command.lvdat.description = 맵의 속성을 변경합니다. pocketmine.command.lvdat.changed = 레벨 "{%0}"의 {%1}을(를) 변경하였습니다. 일부 변경은 서버 재부팅이 필요합니다. pocketmine.command.lvdat.fixname = 성공적으로 레벨 "{%0}"의 이름을 변경하였습니다. 일부 변경은 서버 재부팅이 필요합니다. pocketmine.command.lvdat.nofound = 레벨 "{%0}"이(가) 없거나 로드하지 못했습니다. pocketmine.command.lvdat.preset = 생성기 설정 (프리셋) pocketmine.command.status.description = 서버의 현재 상태를 확인합니다 (TPS 등) pocketmine.command.status.usage = /status pocketmine.command.status.title = 서버 상태 pocketmine.command.status.player = 플레이어 수: pocketmine.command.status.days = 일 pocketmine.command.status.hours = 시간 pocketmine.command.status.minutes = 분 pocketmine.command.status.seconds = 초 pocketmine.command.status.uptime = 작업 시간: pocketmine.command.status.AverageTPS = 평균 TPS: pocketmine.command.status.CurrentTPS = 현재 TPS: pocketmine.command.status.Networkupload = 네트워크 업로드: pocketmine.command.status.Networkdownload = 네트워크 다운로드: pocketmine.command.status.Threadcount = 스레드 수: pocketmine.command.status.Mainmemory = 메인 스레드 메모리: pocketmine.command.status.Totalmemory = 총 메모리: pocketmine.command.status.Totalvirtualmemory = 총 가상 메모리: pocketmine.command.status.Heapmemory = 힙 메모리: pocketmine.command.status.Maxmemorysystem = 최대 메모리 (시스템): pocketmine.command.status.Maxmemorymanager = 최대 메모리 (관리자): pocketmine.command.status.World = 세계 pocketmine.command.status.chunks = 청크, pocketmine.command.status.entities = 엔티티, pocketmine.command.status.tiles = 타일. pocketmine.command.status.Time = 시간 pocketmine.command.status.ms = ms pocketmine.command.gc.description = 가비지 컬렉션 작업을 시작합니다 pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = 컬렉션 보고서 pocketmine.command.gc.chunks = 청크: pocketmine.command.gc.entities = 엔티티: pocketmine.command.gc.tiles = 타일: pocketmine.command.gc.cycles = 주기: pocketmine.command.gc.memory = 릴리스 메모리: pocketmine.command.biome.description = 영역의 바이옴을 변경합니다.(눈 또는 비 변경) pocketmine.command.biome.posset = 위치 {%3}(을)를 다음 위치로 설정하였습니다:({%1},{%2})[{%0}] pocketmine.command.biome.get = 현재 있는 바이옴의 ID는 {%0}입니다. 색상:{%1},{%2},{%3} pocketmine.command.biome.wrongLev = 포인트를 각자 다른 레벨에서 설정할 수 없습니다. pocketmine.command.biome.wrongBio = 잘못된 바이옴의 ID입니다. e.g. 1 (평야), 2 (사막),13 (얼음 산),6 (습지대) pocketmine.command.biome.wrongCol = 잘못된 색입니다. e.g. 146,188,89 ."/biome get"을 사용하여 다른 색상을 가져오세요. pocketmine.command.biome.noPos = 영역을 선택하려면 먼저 "/biome pos1|pos2"를 사용해주세요. pocketmine.command.biome.set = 바이옴을 다음으로 설정하였습니다:{%0} pocketmine.command.biome.color = 색상을 다음으로 설정하였습니다:{%0},{%1},{%2} pocketmine.command.timings.description = 서버 성능 확인을 위해 타이밍을 기록합니다. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = 타이밍 & 초기화 기능을 활성화하였습니다 pocketmine.command.timings.disable = 타이밍이 비활성화되었습니다. pocketmine.command.timings.timingsDisabled = /timings on를 사용하여 타이밍 기능을 활성화해주세요. pocketmine.command.timings.reset = 타이밍을 초기화하였습니다 pocketmine.command.timings.pasteError = 보고 목록에 붙이는 동안 에러가 발생 하였습니다. pocketmine.command.timings.timingsUpload = 타이밍이 {%0}로 업로드가 되었습니다. pocketmine.command.timings.timingsRead = 당신은 {%0} 에서 결과를 읽을 수 있습니다. pocketmine.command.timings.timingsWrite = 타이밍이 {%0}에 작성 되었습니다. pocketmine.command.version.description = 현재 구동되고 있는 서버 버전과 플러그인 정보를 출력합니다. pocketmine.command.version.usage = /version [플러그인 이름] pocketmine.command.version.noSuchPlugin = 입력하신 플러그인을 찾을 수 없습니다. /plugins를 입력해 플러그인 목록을 확인하세요. pocketmine.command.give.description = 해당 플레이어에게 아이템을 지정한 양만큼 줍니다 pocketmine.command.give.usage = /give <플레이어> <아이템[:손상 정도]> [양] [데이터 태그...] pocketmine.command.kill.description = 자신 또는 다른 플레이어를 죽입니다 pocketmine.command.kill.usage = /kill [죽일 플레이어] pocketmine.command.particle.description = 세계에 파티클을 추가합니다. pocketmine.command.particle.usage = /particle <이름> [양] [데이터] pocketmine.command.time.description = 세계 시간을 변경합니다. pocketmine.command.time.usage = /time <값> 또는 /time pocketmine.command.bancidbyname.description = 지정된 플레이어의 CID가 서버에 접속할 수 없게 차단합니다 pocketmine.command.bancid.description = 지정된 CID가 서버에 들어오지 못하게 차단합니다 pocketmine.command.banipbyname.description = 지정된 플레이어의 IP 주소가 서버에 접속할 수 없게 차단합니다 pocketmine.command.ban.player.description = 지정된 플레이어가 서버에 들어오지 못하게 차단합니다 pocketmine.command.ban.ip.description = 지정된 IP 주소가 서버에 접속할 수 없게 차단합니다 pocketmine.command.banlist.description = 이 서버에서 차단한 모든 플레이어 목록을 보여줍니다. pocketmine.command.defaultgamemode.description = 기본 게임모드를 설정합니다 pocketmine.command.deop.description = 지정된 플레이어의 OP 권한을 해제합니다 pocketmine.command.difficulty.description = 게임의 난이도를 설정합니다. pocketmine.command.enchant.description = 아이템에 마법을 부여합니다 pocketmine.command.effect.description = 플레이어에게 효과를 부여하거나 제거합니다. pocketmine.command.gamemode.description = 지정된 플레이어의 게임 모드를 변경합니다 pocketmine.command.help.description = 모든 명령어 목록을 보여줍니다. pocketmine.command.kick.description = 해당 플레이어를 서버에서 퇴장시킵니다 pocketmine.command.list.description = 현재 접속 중인 플레이어의 목록을 보여줍니다 pocketmine.command.me.description = 채팅에서 지정된 동작을 합니다 pocketmine.command.op.description = 지정된 플레이어에게 OP 권한을 부여합니다 pocketmine.command.unban.cid.description = 지정된 CID가 서버를 이용할 수 있도록 차단 해제합니다 pocketmine.command.unban.player.description = 해당 플레이어의 차단을 해제합니다. pocketmine.command.unban.ip.description = 지정된 IP 주소가 서버를 이용할 수 있도록 차단 해제합니다 pocketmine.command.save.description = 서버를 디스크에 저장합니다 pocketmine.command.saveoff.description = 서버 자동 저장을 비활성화합니다 pocketmine.command.saveon.description = 서버 자동 저장을 활성화합니다 pocketmine.command.say.description = 입력된 메시지를 서버 전체에 표시합니다 pocketmine.command.seed.description = 세계의 시드를 보여줍니다. pocketmine.command.setworldspawn.description = 세계의 부활 지점을 지정합니다. 좌표를 입력하지 않으면 현재 플레이어의 좌표로 지정됩니다. pocketmine.command.spawnpoint.description = 플레이어의 부활 지점을 정합니다 pocketmine.command.stop.description = 서버를 정지합니다 pocketmine.command.tp.description = 해당 플레이어 (또는 자신을) 지정한 플레이어나 좌표로 이동시킵니다 pocketmine.command.tell.description = 해당 플레이어에게 개인 메시지를 보냅니다 pocketmine.command.xp.description = 지정한 플레이어에게 경험치 또는 경험치 레벨을 줍니다 pocketmine.command.summon.description = 플레이어의 위치나 지정된 위치에 엔티티를 생성합니다 pocketmine.command.fill.description = 지정한 영역을 블록으로 채웁니다 pocketmine.command.setblock.description = 블록을 다른 블록으로 변경합니다 pocketmine.command.weather.description = 레벨의 날씨를 설정합니다 pocketmine.command.weather.usage = /weather <레벨 이름 또는 날씨|날씨 (rain|sunny|clear)> pocketmine.command.weather.changed = 성공적으로 레벨 {%0}의 날씨를 변경하였습니다! pocketmine.command.weather.noregistered = 레벨 {%0}은(는) 날씨 관리자에 등록되어 있지 않습니다. pocketmine.command.weather.invalid = 잘못된 날씨입니다.(0,1,2,3) pocketmine.command.weather.wrong = 잘못된 매개 변수 입니다. pocketmine.command.weather.invalid.level = 올바르지 않은 레벨 이름입니다. pocketmine.command.whitelist.description = 서버를 이용할 수 있는 플레이어 목록을 관리합니다 pocketmine.crash.create = 복구가 불가능한 오류가 발생하여 서버가 강제로 종료되었습니다. 크래시 덤프를 생성합니다. pocketmine.crash.error = 크래시 덤프를 생성하지 못했습니다: {%0} pocketmine.crash.submit = "{%0}" 파일을 Crash Archive에 업로드하고 버그 리포팅 페이지에 링크를 보내 주세요. 가능한 한 많은 정보를 주시면 감사하겠습니다. pocketmine.crash.archive = 크래시 덤프가 자동으로 Crash Archive에 전송되었습니다. {%0}에서 확인하거나 ID #{%1}을(를) 사용하세요. pocketmine.debug.enable = LevelDB 지원이 활성화되었습니다 pocketmine.player.invalidMove = {%0}님이 잘못된 움직임을 보였습니다! pocketmine.player.logIn = {%0}[/{%1}:{%2}]이(가) 엔티티 ID {%3}(으)로 ({%4}, {%5}, {%6}, {%7})에 접속했습니다 pocketmine.player.logOut = {%0}[/{%1}:{%2}]님의 연결이 끊어졌습니다. 이유: {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}]님이 {%3}(으)로 옮겨졌습니다 pocketmine.player.invalidEntity = {%0}이(가) 유효하지 않은 엔티티를 공격하려 시도했습니다 pocketmine.plugin.load = 플러그인 {%0}을(를) 로딩 중입니다. pocketmine.plugin.enable = 플러그인 {%0}을(를) 활성화 중입니다 pocketmine.plugin.disable = 플러그인 {%0}을(를) 비활성화 중입니다 pocketmine.plugin.restrictedName = 제한된 이름입니다! pocketmine.plugin.incompatibleAPI = 호환되지 않는 API 버전입니다! pocketmine.plugin.unknownDependency = 알 수 없는 의존 플러그인입니다! pocketmine.plugin.circularDependency = 순환 의존성이 탐지되었습니다 pocketmine.plugin.genericLoadError = 플러그인 '{%0}'을(를) 로드하지 못했습니다 pocketmine.plugin.spacesDiscouraged = 플러그인 '{%0}'의 이름에 공백 문자가 섞여 있습니다. 이는 권장되지 않습니다. pocketmine.plugin.loadError = 플러그인 '{%0}'을(를) 로드하지 못했습니다: {%1} pocketmine.plugin.duplicateError = 플러그인 '{%0}'을(를) 로드하지 못했습니다: 이미 존재하는 플러그인입니다 pocketmine.plugin.fileError = 폴더 '{%1}'에서 플러그인 '{%0}'을(를) 로드하지 못했습니다: {%2} pocketmine.plugin.commandError = 명령어 {%0}을(를) 플러그인 {%1}에서 로드하지 못했습니다 pocketmine.plugin.aliasError = 플러그인 {%1}의 명령어 별칭 {%0}을(를) 로드하지 못했습니다 pocketmine.plugin.deprecatedEvent = 플러그인 '{%0}'이(가) 이벤트 '{%1}'을(를) 위한 리스너를 메서드 '{%2}'에 대해 추가했지만, 이 이벤트의 사용은 권장되지 않습니다. pocketmine.plugin.eventError = "이벤트 '{%0}'을(를) 플러그인 '{%1}'에 전달하지 못했습니다: {%2} ({%3})" message.bed.sleep.night = 밤에만 잘 수 있습니다. pocketmine.resourcepacks.createFolder = 리소스팩을 위한 경로 {%0} 가 존재하지 않습니다, 새로운 폴더를 생성합니다... pocketmine.resourcepacks.notFolder = 리소스팩을 위한 경로 {%0} 가 존재하지만, 이것은 폴더 형식이 아닙니다. pocketmine.resourcepacks.load = 리소스팩을 로딩하고 있습니다... pocketmine.resourcepacks.folderNotSupported = {%0} (은)는 폴더의 리소스팩입니다, 아직 지원되지 않습니다. 압축해주세요. pocketmine.resourcepacks.unsupportedType = 리소스팩 형태 {%0} (을)를 알 수 없습니다. 아직 지원되지 않습니다. pocketmine.resourcepacks.packNotFound = 리소스팩 {%0} 이(가) 발견되지 않았습니다. pocketmine.resourcepacks.loadFinished = 리소스팩 {%0} 을(를) 로딩하였습니다. # Language file compatible with Minecraft: Pocket Edition identifiers # # A message doesn't need to be there to be shown correctly on the client. # Only messages shown in PocketMine itself need to be here language.name = Russian language.selected = Выбран {%0} ({%1}) в качестве главного языка multiplayer.player.joined = {%0} присоединился к игре multiplayer.player.left = {%0} вышел из игры chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = Игрок {%0} получил достижение {%1}! disconnectionScreen.notAuthenticated = Требуется авторизация в Xbox! disconnectionScreen.outdatedClient = Устаревший клиент! disconnectionScreen.outdatedServer = Устаревший сервер! disconnectionScreen.serverFull = Сервер переполнен! disconnectionScreen.noReason = Отключен от сервера. disconnectionScreen.invalidSkin = Неверный формат скина! disconnectionScreen.invalidName = Недопустимое имя! death.fell.accident.generic = {%0} упал с высокого места death.attack.inFire = {%0} сгорел death.attack.onFire = {%0} сгорел death.attack.lava = {%0} решил поплавать в лаве death.attack.inWall = {%0} задохнулся в стене death.attack.drown = {%0} утонул death.attack.cactus = {%0} закололся до смерти death.attack.generic = {%0} умер death.attack.explosion = {%0} взорвался death.attack.explosion.player = {%0} был взорван {%1} death.attack.magic = {%0} убит магией death.attack.wither = {%0} иссушен death.attack.mob = {%0} был убит {%1} death.attack.player = {%0} был убит {%1} death.attack.player.item = {%0} был убит {%1} используя {%2} death.attack.arrow = {%0} был застрелен {%1} death.attack.arrow.item = {%0} был застрелен {%1} используя {%2} death.attack.fall = {%0} упал с высокого места death.attack.outOfWorld = {%0} выпал из мира gameMode.survival = Режим выживания gameMode.creative = Творческий режим gameMode.adventure = Приключенческий режим gameMode.spectator = Режим наблюдателя gameMode.changed = Ваш игровой режим был обновлён potion.moveSpeed = Скорость potion.moveSlowdown = Замедление potion.digSpeed = Спешка potion.digSlowDown = Усталость potion.damageBoost = Сила potion.heal = Мгновенное лечение potion.harm = Мгновенный Урон potion.jump = Усиление прыжка potion.confusion = Тошнота potion.regeneration = Регенерация potion.resistance = Сопротивление урону potion.fireResistance = Огнестойкость potion.waterBreathing = Подводное дыхание potion.invisibility = Невидимость potion.blindness = Слепота potion.nightVision = Ночное зрение potion.hunger = Голод potion.weakness = Слабость potion.poison = Отравление potion.wither = Иссушение potion.healthBoost = Повышение здоровья potion.absorption = Поглощение potion.saturation = Насыщенность commands.generic.exception = Произошла неизвестная ошибка при выполнении команды commands.generic.permission = У Вас недостаточно прав для использования этой команды commands.generic.notFound = Неизвестная команда. Используйте /help для списка команд commands.generic.player.notFound = Игрок не найден commands.generic.usage = Использование: {%0} commands.generic.level = level-name commands.generic.seed = seed-name commands.generic.name = name commands.generic.generator = generator-name commands.generic.opt.missing = Требуемые настройки отсутствуют,пожалуйста повторите ввод. commands.generic.runingame = Используйте эту команду в игре. commands.time.added = Добавлено {%0} к времени commands.time.set = Установлено время {%0} commands.time.query = Время ‒ {%0} commands.me.usage = /me <действие ...> commands.give.item.notFound = Предмет с именем {%0} не найден commands.give.success = Дано {%0} * {%1} игроку {%2} commands.give.tagError = Разбор тега данных не удался: {%0} commands.effect.usage = /effect <игрок> <эффект> [кол-во секунд] [уровень] [убратьЧастицы] ИЛИ /effect <игрок> clear commands.effect.notFound = Не существует эффекта с ID {%0} commands.effect.success = Дано {%0} (ID {%1}) * {%2} to {%3} на {%4} секунд commands.effect.success.removed = Эффект {%0} убран у {%1} commands.effect.success.removed.all = Все эффекты были сняты с {%0} commands.effect.failure.notActive = Невозможно убрать {%0} у {%1}, поскольку игрок не имеет данного эффекта commands.effect.failure.notActive.all = Невозможно убрать эффекты у {%0}, потому что игрок не имеет никаких эффектов commands.enchant.noItem = У игрока нет такого предмета commands.enchant.notFound = Нет такого зачарования с ID {%0} commands.enchant.success = Зачарование прошло успешно commands.enchant.cantEnchant = This item cannot be enchanted commands.enchant.usage = /enchant <игрок> [уровень] commands.particle.success = Проигрываются частицы эффекта {%0} {%1} раз commands.particle.notFound = Неизвестный эффект {%0} commands.players.usage = /list commands.players.list = Сейчас {%0}/{%1} игроков на сервере: commands.kill.successful = Убит {%0} commands.banlist.ips = Всего заблокировано %d IP адресов : commands.banlist.players = Всего заблокирован(о) {%0} игрок(ов): commands.banlist.usage = /banlist [ips|players] commands.defaultgamemode.usage = /defaultgamemode <режим игры> commands.defaultgamemode.success = Игровой режим мира по умолчанию ‒ {%0} commands.op.success = {%0} теперь оператор сервера commands.op.usage = /op <игрок> commands.deop.success = {%0} больше не оператор сервера commands.deop.usage = /deop <игрок> commands.say.usage = /say <сообщение ...> commands.seed.usage = /seed commands.seed.success = Сид: {%0} commands.bancidbyname.success = Игрок {%0} заблокирован по номеру устройства commands.bancidbyname.usage = /bancidbyname <ИмяИгрока> commands.bancid.success = Заблокирован номер устройства: {%0} commands.bancid.usage = /bancid <НомерУстройства> commands.unbancid.usage = /pardoncid <НомерУстройства> commands.ban.success = Заблокирован игрок {%0} commands.ban.usage = /ban <ИмяИгрока> [причина ...] [время(день)] commands.unban.success = Разблокирован игрок {%0} commands.unban.usage = /pardon <имя> commands.banip.invalid = Вы ввели неправильный IP-адрес или данный игрок не в сети commands.banip.success = Заблокирован IP адрес {%0} commands.banip.success.players = Заблокирован IP адрес {%0} принадлежащий {%1} commands.banip.usage = /ban-ip [причина ...] commands.unbanip.invalid = Вы ввели неправильный IP адрес commands.unbanip.success = Разблокирован IP адрес {%0} commands.unbanip.usage = /pardon-ip <адрес> commands.banipbyname.success = Игрок с ником {%0} забанен по IP commands.banipbyname.usage = /banipbyname commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = Включено авто-сохранение commands.save.disabled = Автоматическое сохранение мира отключено commands.save.start = Сохранение... commands.save.success = Мир сохранён commands.setblock.usage = /setblock <ИмяБлока> [Урон] command.setblock.invalidBlock = Некорректное имя блока/айди commands.stop.usage = /stop commands.stop.start = Остановка сервера commands.kick.success = Кикнут {%0} с сервера commands.kick.success.reason = Кикнут {%0} с сервера: '{%1}' commands.kick.usage = /kick <игрок> [причина ...] commands.tp.success = Телепортирован {%0} к {%1} commands.tp.success.coordinates = Телепортирован {%0} на {%1}, {%2}, {%3} commands.tp.usage = /tp [целевой игрок] <назначенный игрок> или /tp [целевой игрок] [ ] commands.whitelist.list = В вайтлисте {%0} игроков (из {%1} отображаемых): commands.whitelist.enabled = Вайтлист включен commands.whitelist.disabled = Вайтлист выключен commands.whitelist.reloaded = Список разрешенных игроков перезагружен commands.whitelist.add.success = {%0} добавлен в вайтлист commands.whitelist.add.usage = /whitelist add <игрок> commands.whitelist.remove.success = {%0} убран из вайтлиста commands.whitelist.remove.usage = /whitelist remove <игрок> commands.whitelist.usage = /whitelist commands.gamemode.success.self = Установлен режим игры {%2} для себя commands.gamemode.success.other = Игровой режим игрока {%0} изменён на {%1} commands.gamemode.usage = /gamemode <игровой режим> [игрок] commands.help.header = --- Отображается страница {%0} из {%1} (/help <страница>) --- commands.help.usage = /help [страница|имя команды] commands.message.usage = /tell <игрок> <личное сообщение...> commands.message.sameTarget = Вы не можете отправить сообщение самому себе! commands.xp.usage = /xp <Опыт или Уровень опыта + Число> <Игрок> commands.difficulty.usage = /difficulty <сложность игры> commands.difficulty.success = Установлена сложность игры на {%0} commands.spawnpoint.usage = /spawnpoint [игрок] [ ] commands.spawnpoint.success = Установлена точка респауна игрока {%0} ({%1}, {%2}, {%3}) commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = Установлен респаун мира на ({%0}, {%1}, {%2}) commands.summon.usage = /summon [сущность] [ ] [Имя] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = Информация об игроке "{%0}" не найдена, создаётся новый профиль pocketmine.data.playerCorrupted = Повреждённые данные у игрока "{%0}", создание нового профиля pocketmine.data.playerOld = Обнаружен старый профиль игрока "{%0}", обновляем на новый pocketmine.data.saveError = Невозможно сохранить игрока "{%0}": {%1} pocketmine.level.notFound = Мир "{%0}" не найден pocketmine.level.loadError = Невозможно загрузить мир"{%0}": {%1} pocketmine.level.generationError = Невозможно сгенерировать уровень "{%0}": {%1} pocketmine.level.tickError = Ошибка прорисовки мира "{%0}": {%1} pocketmine.level.chunkUnloadError = Ошибка при выгрузке чанка: {%0} pocketmine.level.backgroundGeneration = Генерируется территория спауна для мира "{%0}" pocketmine.level.defaultError = Мир по умолчанию не загружен pocketmine.level.preparing = Подготовка мира "{%0}" pocketmine.level.unloading = Выгрузка мира "{%0}" pocketmine.server.start = Запускается сервер Minecraft: PE версии {%0} pocketmine.server.networkError = [Сеть] Остановлен интерфейс {%0} из-за {%1} pocketmine.server.networkStart = Открытие сервера на {%0}:{%1} pocketmine.server.info.extended = Сервер использует {%0} {%1}「 {%2}」 , версия API {%3} для Minecraft: PE {%4} (версия протокола {%5}) pocketmine.server.info = Этот сервер использует {%0}, версию {%1} "{%2}" (API {%3}) pocketmine.server.license = {%0} распространяется под лицензией LGPL pocketmine.server.tickOverload = Сервер перегружен! pocketmine.server.startFinished = Загружено ({%0} секунд)! Для справки введите "help" или "?" pocketmine.server.defaultGameMode = Игровой режим по умолчанию: {%0} pocketmine.server.query.start = Запуск прослушивателя статуса GS4 pocketmine.server.query.info = Установлен порт query на {%0} pocketmine.server.query.running = Query запущен на {%0}:{%1} pocketmine.command.alias.illegal = Невозможно зарегистрировать альтернативу команды {%0}, поскольку он содержит недопустимые знаки pocketmine.command.alias.notFound = Невозможно зарегистрировать альтернативу команды {%0}, поскольку он содержит недопустимые команды: {%1} pocketmine.command.exception = Необработанное исключение при выполнении команды '{%0}' в {%1}: {%2} pocketmine.commands.cave.usage = /cave <угол_поворота> <длина> <Номер ветви> <прочность> <название_мира> | /cave getmypos pocketmine.commands.cave.info = Угол поворота:{%0} Длина:{%1} Номер ветви:{%2} Прочность:{%3} pocketmine.commands.cave.start = Начало создания пещеры, пожалуйста, подождите! pocketmine.command.plugins.description = Получить список установленых плагинов на этом сервере pocketmine.command.plugins.success = Плагины ({%0}): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Перезагрузить настройки сервера и плагины pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Перезагрузка сервера... pocketmine.command.reload.reloaded = Перезагрузка завершена. pocketmine.command.lvdat.description = Изменить настройки карты. pocketmine.command.lvdat.changed = Был сменена настройка {%1} для мира "{%0}", для применения некоторых изменений нужна перезагрузка. pocketmine.command.lvdat.fixname = Имя для мира "{%0}" было успешно исправлено, для применения некоторых изменений нужна перезагрузка. pocketmine.command.lvdat.nofound = Мир "{%0}" не найден или не загружен. pocketmine.command.lvdat.preset = Настройки генератора (пресеты) pocketmine.command.status.description = Отображает производительность сервера. pocketmine.command.status.usage = /status pocketmine.command.status.title = Статус сервера pocketmine.command.status.player = Число игроков: pocketmine.command.status.days = дн. pocketmine.command.status.hours = ч pocketmine.command.status.minutes = мин pocketmine.command.status.seconds = сек pocketmine.command.status.uptime = Аптайм: pocketmine.command.status.AverageTPS = Средний TPS: pocketmine.command.status.CurrentTPS = Текущий TPS: pocketmine.command.status.Networkupload = Данных отправлено: pocketmine.command.status.Networkdownload = Данных получено: pocketmine.command.status.Threadcount = Количество потоков: pocketmine.command.status.Mainmemory = Основная память потока: pocketmine.command.status.Totalmemory = Общая память: pocketmine.command.status.Totalvirtualmemory = Общая виртуальная память: pocketmine.command.status.Heapmemory = Heap memory: pocketmine.command.status.Maxmemorysystem = Максимальная память (системы): pocketmine.command.status.Maxmemorymanager = Максимальная память (менеджер): pocketmine.command.status.World = Мир pocketmine.command.status.chunks = чанков, pocketmine.command.status.entities = сущностей, pocketmine.command.status.tiles = тайлов. pocketmine.command.status.Time = Время pocketmine.command.status.ms = мс pocketmine.command.gc.description = Запускает процессы сборщика мусора pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = Отчёт очистки pocketmine.command.gc.chunks = Чанков: pocketmine.command.gc.entities = Сущностей: pocketmine.command.gc.tiles = Тайлов: pocketmine.command.gc.cycles = Циклов: pocketmine.command.gc.memory = Освобождённая память: pocketmine.command.biome.description = Изменяет биом выделенной области. (Для смены снега на дождь) pocketmine.command.biome.posset = Установлена точка {%3} на позиции: ({%1},{%2})[{%0}] pocketmine.command.biome.get = ID биома, где ты стоишь: {%0}. Цвет:{%1},{%2},{%3} pocketmine.command.biome.wrongLev = Нельзя устанавливать точки в разнымх мирах. pocketmine.command.biome.wrongBio = Неверный ID биома. Пример: 1 (Равнины), 2 (Пустыня),13 (Ледяные горы),6 (Болото) pocketmine.command.biome.wrongCol = Неверный цвет! Пример: 146,188,89. Используй: "/biome get" чтобы получить остальные цвета. pocketmine.command.biome.noPos = Используйте "/biome pos1|pos2" чтобы отметить точки. pocketmine.command.biome.set = Был установлен цвет:{%0} pocketmine.command.biome.color = Выбран цвет: {%0},{%1},{%2} pocketmine.command.timings.description = Записывает тайминги, чтобы показать производительность сервера. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Тайминги включены и сброшены. pocketmine.command.timings.disable = Тайминги выключены pocketmine.command.timings.timingsDisabled = Пожалуйста, включите тайминги, набрав в чате /timings on pocketmine.command.timings.reset = Тайминги сброшены pocketmine.command.timings.pasteError = Произошла ошибка при сохранении таймингов pocketmine.command.timings.timingsUpload = Тайминги загружены в {%0} pocketmine.command.timings.timingsRead = Вы можете прочесть результаты таймингов в {%0} pocketmine.command.timings.timingsWrite = Тайминги записаны в {%0} pocketmine.command.version.description = Получает версию сервера, включая все используемые плагины pocketmine.command.version.usage = /version [имя плагина] pocketmine.command.version.noSuchPlugin = Этот сервер не использует плагин с таким именем. Используйте /plugins, чтобы получить список используемых плагинов. pocketmine.command.give.description = Даёт определённому игроку определённое количество предметов pocketmine.command.give.usage = /give <игрок> <предмет[:урон]> [количество] [тэги...] pocketmine.command.kill.description = Совершает суицид или убивает других игроков pocketmine.command.kill.usage = /kill [игрок] pocketmine.command.particle.description = Добавляет частицы в мир pocketmine.command.particle.usage = /particle <имя> [значение] [данные] pocketmine.command.time.description = Изменяет время в каждом мире pocketmine.command.time.usage = /time ИЛИ /time pocketmine.command.bancidbyname.description = Бан игрока по уникальному номеру устройства pocketmine.command.bancid.description = Бан уникального номера устройства pocketmine.command.banipbyname.description = Бан игрока по IP pocketmine.command.ban.player.description = Бан игрока pocketmine.command.ban.ip.description = Бан IP-адреса pocketmine.command.banlist.description = Список забаненных игроков pocketmine.command.defaultgamemode.description = Устанавливает режим игры по умолчанию pocketmine.command.deop.description = Убирает статус оператора с определённого игрока pocketmine.command.difficulty.description = Устанавливает сложность игры pocketmine.command.enchant.description = Добавляет зачарование предметам pocketmine.command.effect.description = Добавляет/Убирает эффекты у игроков pocketmine.command.gamemode.description = Изменяет режим игры у игрока pocketmine.command.help.description = Показывает меню помощи pocketmine.command.kick.description = Заставить выйти определённого игрока с сервера pocketmine.command.list.description = Показывает всех игроков на сервере pocketmine.command.me.description = Выполняет указанное действие в чате pocketmine.command.op.description = Даёт определённому игроку статус оператора pocketmine.command.unban.cid.description = Разбан уникального номера устройства игрока pocketmine.command.unban.player.description = Разбан игрока по нику pocketmine.command.unban.ip.description = Разбан IP-адреса игрока pocketmine.command.save.description = Сохраняет все файлы сервера pocketmine.command.saveoff.description = Отключает автосохранение сервера pocketmine.command.saveon.description = Включает автосохранение сервера pocketmine.command.say.description = Транслирует сообщение в чат pocketmine.command.seed.description = Показывает сид мира pocketmine.command.setworldspawn.description = Устанавливает точку респауна мира. Если координаты не введены, будут использованы координаты игрока. pocketmine.command.spawnpoint.description = Устанавливает точку возрождения игрока pocketmine.command.stop.description = Выключает сервер pocketmine.command.tp.description = Телепортирует определённого игрока (или Вас) к другому игроку или на другие координаты pocketmine.command.tell.description = Отправляет приватное сообщение указанному игроку pocketmine.command.xp.description = Добавляет опыт или уровень опыта игроку pocketmine.command.summon.description = Создаёт сущность по установленным координатам или координатом игрока pocketmine.command.fill.description = Заполняет выделенную область блоками pocketmine.command.setblock.description = Заменяет блок по координатам pocketmine.command.weather.description = Установка погоды в мире pocketmine.command.weather.usage = /weather <мир> (0,1,2,3) pocketmine.command.weather.changed = Погода успешно изменена в мире {%0}! pocketmine.command.weather.noregistered = Мир {%0} не был зарегистрирован в менеджере погоды. pocketmine.command.weather.invalid = Некорректная погода.(0,1,2,3) pocketmine.command.weather.wrong = Некорректные параметры. pocketmine.command.weather.invalid.level = Некорректное имя мира. pocketmine.command.whitelist.description = Управление вайтлистом сервера pocketmine.crash.create = Произошла фатальная ошибка и сервер вышел из строя. Создание аварийного дампа pocketmine.crash.error = Не удалось создать дамп: {%0} pocketmine.crash.submit = Пожалуйста, загрузите файл"{%0}" в краш-архив и отправьте ссылку на страницу исправления ошибок. Дайте как можно больше информации. pocketmine.crash.archive = Дамп выхода из строя сервера был автоматически отправлен в краш-архив. Вы можете его просмотреть тут: {%0}, или использовать ID #{%1}. pocketmine.debug.enable = Поддержка LevelDB включена pocketmine.player.invalidMove = {%0} некорректно передвинулся! pocketmine.player.logIn = {%0}[/{%1}:{%2}] вошел с id сущности {%3} на ({%4}, {%5}, {%6}, {%7}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] отключился: {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}] был перемещен в {%3} pocketmine.player.invalidEntity = {%0} попытался атаковать неправильную сущность pocketmine.plugin.load = Загрузка {%0} pocketmine.plugin.enable = Включение {%0} pocketmine.plugin.disable = Выключение {%0} pocketmine.plugin.restrictedName = Ограниченное имя pocketmine.plugin.incompatibleAPI = Несовместимая версия API pocketmine.plugin.unknownDependency = Неизвестная зависимость pocketmine.plugin.circularDependency = Обнаружена круговая зависимость pocketmine.plugin.genericLoadError = Невозможно загрузить плагин '{%0}' pocketmine.plugin.spacesDiscouraged = Плагин '{%0}' использует пробелы в его имени, это недопустимо pocketmine.plugin.loadError = Невозможно загрузить плагин '{%0}': {%1} pocketmine.plugin.duplicateError = Невозможно загрузить '{%0}': плагин уже существует pocketmine.plugin.fileError = Невозможно загрузить '{%0}' в папке '{%1}': {%2} pocketmine.plugin.commandError = Невозможно загрузить команду {%0} для плагина {%1} pocketmine.plugin.aliasError = Невозможно загрузить сокращение {%0} для плагина {%1} pocketmine.plugin.deprecatedEvent = Плагин '{%0}' зарегистрировал прослушивателя для '{%1}', используя метод '{%2}', но событие было отменено. pocketmine.plugin.eventError = "Невозможно обработать событие '{%0}' в '{%1}': {%2} в {%3}" language.name = Türkçe language.selected = {%0} ({%1}) konsol dili olarak seçildi multiplayer.player.joined = {%0} oyuna katıldı multiplayer.player.left = {%0} oyundan ayrıldı chat.type.achievement = {%0} adlı oyuncu {%1} başarımını kazandı disconnectionScreen.notAuthenticated = Xbox girişi yapmadınız disconnectionScreen.outdatedClient = Eski sürüm! disconnectionScreen.outdatedServer = Sunucu daha eski bir sürümde! disconnectionScreen.serverFull = Sunucu dolu! disconnectionScreen.noReason = Sunucu ile olan bağlantınız kesildi disconnectionScreen.invalidSkin = Geçersiz skin! disconnectionScreen.invalidName = Geçersiz isim! death.fell.accident.generic = {%0} yüksekten düşerek öldü death.attack.inFire = {%0} yanarak öldü death.attack.onFire = {%0} yanarak can verdi death.attack.lava = {%0} lavda yüzmeye çalıştı death.attack.inWall = {%0} duvarda sıkışarak öldü death.attack.drown = {%0} boğuldu death.attack.cactus = {%0} delinerek öldü death.attack.generic = {%0} öldü death.attack.explosion = {%0} patlayarak öldü death.attack.explosion.player = {%0} adlı oyuncu {%1} tarafından havaya uçuruldu death.attack.magic = {%0} sihirlenerek öldürüldü death.attack.wither = {%0} witherlanarak öldü death.attack.mob = {%0} adlı oyuncu {%1} tarafından öldürüldü death.attack.player = {%0} adlı oyuncu {%1} tarafından öldürüldü death.attack.player.item = {%0} adlı oyuncu {%1} tarafından {%2} kullanılarak öldürüldü death.attack.arrow = {%0} adlı oyuncu {%1} tarafından vurularak öldürüldü death.attack.arrow.item = {%0} adlı oyuncu {%1} adlı oyuncuyu {%2} kullanarak vurdu death.attack.fall = {%0} yüksekten düşerek öldü death.attack.outOfWorld = {%0} dünyadan aşağı düşerek can verdi gameMode.survival = Hayatta Kalma Modu gameMode.creative = Yaratıcı Mod gameMode.adventure = Maceracı Mod gameMode.spectator = İzleyici Mod gameMode.changed = Oyun modunuz güncellendi potion.moveSpeed = Hız potion.moveSlowdown = Yavaşlık potion.digSpeed = Sürat potion.digSlowDown = Kazı Yorgunluğu potion.damageBoost = Güç potion.heal = Anlık Can potion.harm = Anlık Zarar potion.jump = Zıplama Artışı potion.confusion = Bulantı potion.regeneration = Yenilenme potion.resistance = Dayanıklılık potion.fireResistance = Ateş Dayanıklılığı potion.waterBreathing = Suda Nefes Alma potion.invisibility = Görünmezlik potion.blindness = Körlük potion.nightVision = Gece Görüşü potion.hunger = Açlık potion.weakness = Zayıflık potion.poison = Zehir potion.wither = Wither potion.healthBoost = Can Artışı potion.absorption = Emme potion.saturation = Doyma commands.generic.exception = Komut uygulanırken bilinmeyen bir hata oluştu commands.generic.permission = Bu komutu kullanabilmek için yetkiniz yok commands.generic.notFound = Bilinmeyen komut. Tüm komutları görmek için /help yazınız commands.generic.player.notFound = Belirtilen oyuncu bulunamadı commands.generic.usage = Kullanımı: {%0} commands.generic.level = level-name commands.generic.seed = seed-name commands.generic.name = name commands.generic.generator = generator-name commands.generic.opt.missing = Eksik ayarlar, lütfen onaylayıp yeniden girin. commands.generic.runingame = Bu komutu lütfen oyundayken kullanın. commands.time.added = Saate {%0} eklendi commands.time.set = Dünya saati {%0} olarak ayarlandı commands.time.query = Dünya saati: {%0} commands.me.usage = /me commands.give.item.notFound = Belirtilen eşya bulunamıyor: {%0} commands.give.success = {%2} adlı oyuncuya {%1} tane {%0} eşyası verildi commands.give.tagError = Veri başlığı ayrıştırması başarısız oldu: {%0} commands.effect.usage = /effect [saniye] [yükselme] [EfektleriGizle] yada /effect clear commands.effect.notFound = Bilinmeyen efekt: {%0} commands.effect.success = {%3} adlı oyuncuya {%4} saniyeliğine {%2} gücünde {%0} (ID {%1}) verildi commands.effect.success.removed = {%0} adlı efekt {%1} adlı oyuncudan alındı commands.effect.success.removed.all = {%0} adlı oyuncunun bütün efektleri alındı commands.effect.failure.notActive = {%0} adlı oyuncudan {%1} efekti alınamadı. Çünkü bu efekte sahip değil commands.effect.failure.notActive.all = {%0} adlı oyuncunun efektleri alınamadı. Çünkü hiç bir efekte sahip değil commands.enchant.maxLevel = Bu büyünün level menzili 1 - {%0} arasında olmalıdır commands.enchant.noItem = Hedef elinde bir eşya tutmamaktadır commands.enchant.notFound = {%0} kodunda bir büyü bulunmamakta commands.enchant.success = Büyü başarılı commands.enchant.cantEnchant = Belirtilen büyü hedefin eşyasına eklenemez commands.enchant.usage = /enchant [seviye] commands.particle.success = {%0} efekti {%1} kere oynatılıyor commands.particle.notFound = {%0} - Bilinmeyen efekt ismi commands.players.usage = /list commands.players.list = Çevrimiçi oyuncular: {%0}/{%1} commands.kill.successful = {%0} adlı oyuncu öldürüldü commands.banlist.ips = Toplam %d adet engellenmiş IP adresi var: commands.banlist.players = Toplam {%0} adet engellenmiş oyuncu var: commands.banlist.cids = Toplam {%0} adet CID engellenmiş oyuncu var: commands.banlist.usage = /banlist [ips|players|cids] commands.defaultgamemode.usage = /defaultgamemode commands.defaultgamemode.success = Dünyanın varsayılan oyun modu {%0} olarak ayarlandı commands.op.success = {%0} adlı oyuncuya OP yetkisi verildi commands.op.usage = /op commands.deop.success = {%0} adlı oyuncunun OP durumu alındı commands.deop.usage = /deop commands.say.usage = /say commands.seed.usage = /seed commands.seed.success = Seed: {%0} commands.bancidbyname.success = {%0} adlı oyuncunun CihazID'si engellendi commands.bancidbyname.usage = /bancidbyname commands.bancid.success = {%0} adlı oyuncunun CihazID'si engellendi commands.bancid.usage = /bancid commands.unbancid.usage = /pardoncid commands.unbancid.success = {%0} kodlu CihazID engeli kaldırıldı commands.ban.success = {%0} adlı oyuncu engellendi commands.ban.usage = /ban [sebep..] [zaman(gün)] commands.unban.success = {%0} adlı oyuncunun engeli kaldırıldı commands.unban.usage = /pardon commands.banip.invalid = Bilinmeyen IP adresi veya çevrimiçi olmayan oyuncu adı girdiniz commands.banip.success = {%0} IP adresi engellendi commands.banip.success.players = Engellenen IP adresi {%0}, {%1} kullanıcısına ait commands.banip.usage = /ban-ip [sebep ...] commands.unbanip.invalid = Geçersiz IP adresi girdiniz commands.unbanip.success = {%0} IP adresinin engeli kaldırıldı commands.unbanip.usage = /pardon-ip commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = Otomatik kaydetme özelliği etkinleştirildi commands.save.disabled = Otomatik kaydetme özelliği devre dışı commands.save.start = Kaydediliyor... commands.save.success = Dünya kaydedildi commands.setblock.usage = /setblock [veri] command.setblock.invalidBlock = Bilinmeyen BlokAdı veya ID'si girdiniz commands.stop.usage = /stop commands.stop.start = Sunucu durduruluyor commands.kick.success = {%0} oyundan atıldı commands.kick.success.reason = {%0} oyundan atıldı: '{%1}' commands.kick.usage = /kick [sebep ...] commands.tp.success = {%0}, {%1} adlı oyuncuya ışınlandı commands.tp.success.coordinates = {%0} adlı oyuncu {%1}, {%2}, {%3} koordinatlarına ışınlandı commands.tp.usage = /tp [oyuncu1] yada /tp [oyuncu] [ ] commands.whitelist.list = Toplam {%0} (görünen {%1}) whitelist oyuncusu var: commands.whitelist.enabled = Whitelist aktif edildi commands.whitelist.disabled = Whitelist deaktif edildi commands.whitelist.reloaded = Whitelist yeniden yüklendi commands.whitelist.add.success = {%0} adlı oyuncu whitelist listesine eklendi commands.whitelist.add.usage = /whitelist add commands.whitelist.remove.success = {%0} adlı oyuncu whitelist listesinden kaldırıldı commands.whitelist.remove.usage = /whitelist remove commands.whitelist.usage = /whitelist commands.gamemode.success.self = Oyun modunuz {%2} moduna değiştirildi commands.gamemode.success.other = {%0} adlı oyuncunun oyun modu {%1} moduna değiştirildi commands.gamemode.usage = /gamemode [oyuncu] commands.help.header = --- Yardım sayfası {%0} / {%1} (/help ) --- commands.help.usage = /help [sayfa|komut adı] commands.message.usage = /tell commands.message.sameTarget = Kendinize mesaj yollayamazsınız! commands.difficulty.usage = /difficulty commands.difficulty.success = Oyun zorluk seviyesi başarıyla değiştirildi! commands.spawnpoint.usage = /spawnpoint [oyuncu] [ ] commands.spawnpoint.success = {%0} adlı oyuncunun başlangıç noktası ({%1}, {%2}, {%3}) koordinatlarına olarak değiştirildi commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = Dünya başlangıç noktası ({%0}, {%1}, {%2}) koordinatlarına değiştirildi commands.summon.usage = /summon [entity] [ ] [NBTBaşlığı] commands.xp.failure.withdrawXp = Oyunculara negatif level değeri veremezsiniz commands.xp.success = {%1} adlı oyuncuya {%0} tecrübe puanı verildi commands.xp.success.levels = {%1} adlı oyuncuya {%1} seviye verildi commands.xp.success.negative.levels = {%1} adlı oyuncudan {%0} seviye alındı commands.xp.usage = /xp [oyuncu] veya /xp L [oyuncu] pocketmine.data.playerNotFound = Oyuncu verisi bulunamadı: "{%0}", yeni profil oluşturuluyor pocketmine.data.playerCorrupted = Bozuk veri "{%0}" bulunamadı, yeni profil oluşturuluyor pocketmine.data.playerOld = Eski veri "{%0}" bulundu, profil güncelleniyor pocketmine.data.saveError = Oyuncu kaydedilemiyor "{%0}": {%1} pocketmine.level.notFound = Dünya "{%0}" bulunamadı pocketmine.level.loadError = Dünya yüklenmiyor "{%0}": {%1} pocketmine.level.generationError = Dünya oluşturulamıyor "{%0}": {%1} pocketmine.level.tickError = Dünya işaretlenemiyor "{%0}": {%1} pocketmine.level.chunkUnloadError = Chunk yüklemesi kaldırılırken hata: {%0} pocketmine.level.backgroundGeneration = Spawn bölgesi "{%0}" oluşturulmaya başlandı pocketmine.level.defaultError = Varsayılan dünya yüklenemedi pocketmine.level.preparing = Dünya hazırlanıyor: "{%0}" pocketmine.level.unloading = Dünya yüklemesi kaldırılıyor "{%0}" pocketmine.server.start = Minecraft: PE sunucu versiyonu {%0} başlatılıyor pocketmine.server.networkError = [Şebeke] Arayüz {%1} için {%0} nedeniyle durduruldu pocketmine.server.networkStart = Sunucu {%0}:{%1} adresinde açılıyor pocketmine.server.info = Bu sunucu {%0} versiyonunda {%1} "{%2}" (API {%3}) çalışıyor pocketmine.server.info.extended = This server is running {%0} {%1} 「{%2}」 implementing API version {%3} for Minecraft: PE {%4} (protocol version {%5}) pocketmine.server.license = {%0} is distributed under the LGPL License pocketmine.server.tickOverload = HATA! Sunucu aşırı mı yüklendi? pocketmine.server.startFinished = {%0} saniye içinde tamamlandı. Yardım için /help yazınız pocketmine.server.defaultGameMode = Varsayılan oyun modu: {%0} pocketmine.server.query.start = GS4 durum dinleyicisi başlatılıyor pocketmine.server.query.info = Query port kuruluyor: {%0} pocketmine.server.query.running = Şurada sorgulanıyor: {%0}:{%1} pocketmine.command.alias.illegal = {%0} kayıt edilemiyor. Çünkü yasaklı karakterler içeriyor pocketmine.command.alias.notFound = Takma ad {%0} bulunamadı. Çünkü olmayan bu komutları içerir: {%1} pocketmine.command.exception = İşlenmeyen işletme komutu: '{%0}' 'de {%1}: {%2} pocketmine.command.plugins.description = Sunucuda çalışan eklentileri gösterir pocketmine.command.plugins.success = Eklentiler ({%0}): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Sunucu ayarlarını ve eklentilerini yeniden yükler pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Sunucu yeniden yükleniyor... pocketmine.command.reload.reloaded = Yeniden yükleme tamamlandı. pocketmine.command.status.description = Sunucunun performansını geri okur. pocketmine.command.status.usage = /status pocketmine.command.gc.description = Toplanmış atık dosyaları yakar pocketmine.command.gc.usage = /gc pocketmine.command.timings.description = Timings'i sunucunun performansını görmek için kaydeder. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Timings & Reset aktif edildi pocketmine.command.timings.disable = Timings devre dışı bırakıldı pocketmine.command.timings.timingsDisabled = Timings'i aktif etmek için /timings on yazınız pocketmine.command.timings.reset = Timigs yeniden başlatma pocketmine.command.timings.pasteError = Raporu geçerken bir hata meydana geldi pocketmine.command.timings.timingsUpload = Timings şuraya yüklendi: {%0} pocketmine.command.timings.timingsRead = Sonuçları {%0}'da okuyabilirsin pocketmine.command.timings.timingsWrite = Timings şuraya yazıldı: {%0} pocketmine.command.version.description = Sunucunun ve kullanımda olan eklentilerin versiyonunu alırsınız pocketmine.command.version.usage = /version [eklenti adı] pocketmine.command.version.noSuchPlugin = Bu sunucu bu isimde olan bir eklenti çalıştıramaz. /plugins yazarak eklenti listesine bakabilirsiniz pocketmine.command.give.description = Belirtilen oyuncuya belli miktarda eşya verir pocketmine.command.give.usage = /give [miktar] [başlıklar..] pocketmine.command.kill.description = Kendini veya diğer oyuncuları öldür pocketmine.command.kill.usage = /kill [oyuncu] pocketmine.command.particle.description = Dünyaya parçacık efekti eklendi pocketmine.command.particle.usage = /particle [adet] [data] pocketmine.command.time.description = Her bir dünyadaki saati değiştir pocketmine.command.time.usage = /time veya /time pocketmine.command.ban.player.description = Oyuncuyu sunucudan engeller pocketmine.command.ban.ip.description = Belirlenen IP adresini sunucudan engelller pocketmine.command.banlist.description = Sunucudan engellenen oyuncuları gör pocketmine.command.defaultgamemode.description = Varsayılan oyun modunu ayarla pocketmine.command.deop.description = Belirlenen oyuncunun OP durumunu alır pocketmine.command.difficulty.description = Varsayılan oyun zorluğunu ayarla pocketmine.command.enchant.description = Eşyalara büyü ekler pocketmine.command.effect.description = Oyuncuya efekt ekle veya kaldır pocketmine.command.gamemode.description = Belirlenen oyuncunun oyun modunu değiştirir pocketmine.command.help.description = Yardım menüsünü gösterir pocketmine.command.kick.description = Belirlenen oyuncuyu sunucudan atar pocketmine.command.list.description = Çevrimiçi oyuncular listesi pocketmine.command.me.description = Yaptığınız aksiyonu sohbette anlatır pocketmine.command.op.description = Belirlenen oyuncuya OP verir pocketmine.command.unban.player.description = Belirlenen oyuncunun sunucuya girme engelini açar pocketmine.command.unban.ip.description = Belirlenen IP adresinin sunucuya girme engelini açar pocketmine.command.save.description = Sunucuyu diske kaydeder pocketmine.command.saveoff.description = Sunucunun otomatik kaydetmesini deaktif eder pocketmine.command.saveon.description = Sunucunun otomatik kaydetmesini aktif eder pocketmine.command.say.description = Belirlenen mesajı gönderici olarak yayınlar pocketmine.command.seed.description = Dünyanın seed'ini gösterir pocketmine.command.setworldspawn.description = Dünya başlangıç noktasını ayarlar. pocketmine.command.spawnpoint.description = Oyuncunun başlangıç noktasını ayarlar pocketmine.command.stop.description = Sunucuyu durdurur pocketmine.command.tp.description = Belirlenen oyuncuya ışınlar veya belirlenen oyuncuyu başka bir oyuncuya ışınlar pocketmine.command.tell.description = Belirlenen oyuncuya özel mesaj yollar pocketmine.command.whitelist.description = Sunucuya girme hakkı olan oyuncuları ayarlar pocketmine.crash.create = Bilinmeyen bir hata oluştu ve sunucu çöktü. Çökme arşivi oluşturuluyor pocketmine.crash.error = Çökme arşivi oluşturulamadı: {%0} pocketmine.crash.submit = Lütfen "{%0}" dosyasını Çökme Arşivine yükleyin ve linki Bug Raporlama sayfasına gönderin. Size bir çok bilgi sunulacak. pocketmine.crash.archive = Çökme bilgisi Çökme Arşivine otomatik olarak gönderildi. Onu {%0}'da görüntüleyebilirsin veya ID #{%1}'i kullanabilirsin. pocketmine.debug.enable = LevelDB yardımı aktif edildi pocketmine.player.invalidMove = {%0} moved wrongly! pocketmine.player.logIn = {%0}[/{%1}:{%2}], {%3} Entity ID'si ile ({%4}, {%5}, {%6}, {%7})'da giriş yaptı pocketmine.player.logOut = {%0}[/{%1}:{%2}], {%3} sebebiyle çıkış yaptı pocketmine.player.invalidEntity = {%0} bilinmeyen bir varlığa saldırmayı denedi pocketmine.plugin.load = {%0} Yükleniyor pocketmine.plugin.enable = {%0} aktif ediliyor pocketmine.plugin.disable = {%0} deaktif ediliyor pocketmine.plugin.restrictedName = Yasak ad pocketmine.plugin.incompatibleAPI = Uyumsuz API versiyonu pocketmine.plugin.unknownDependency = Bilinmeyen bağımlı pocketmine.plugin.circularDependency = Dairesel bağımlı tespit edildi pocketmine.plugin.genericLoadError = Eklenti yüklenemiyor '{%0}' pocketmine.plugin.spacesDiscouraged = '{%0}' eklentisi isminde boşluk barındırıyor, vazgeçildi pocketmine.plugin.loadError = Eklenti yüklenemedi '{%0}': {%1} pocketmine.plugin.duplicateError = '{%0}' adlı eklenti zaten var olduğundan yüklenemedi pocketmine.plugin.fileError = '{%0}' eklentisi '{%1}': {%2} dosyasında yüklenemedi pocketmine.plugin.commandError = {%0} komutu {%1} eklentisi için yüklenemedi pocketmine.plugin.aliasError = {%0} takma adı {%1} eklentisi için yüklenemedi pocketmine.plugin.deprecatedEvent = '{%0}' eklentisi bir '{%1}' dinleyicisi için '{%2}' yönteminde kayıt oldu, ama durum önerilmiyor. pocketmine.plugin.eventError = "'{%0}' olayı '{%1}': {%2} 'ye {%3}'de geçilemiyor" # Language file compatible with Minecraft: Pocket Edition identifiers # # A message doesn't need to be there to be shown correctly on the client. # Only messages shown in PocketMine itself need to be here language.name = Ukrainian language.selected = Обрано {%0} ({%1}) в якості основної мови multiplayer.player.joined = {%0} приєднався до гри multiplayer.player.left = {%0} покинув гру chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = Гравець {%0} отримав досягнення {%1}! disconnectionScreen.notAuthenticated = Необхідно авторизуватися в Xbox! disconnectionScreen.outdatedClient = Застарілий клієнт! disconnectionScreen.outdatedServer = Застарілий сервер! disconnectionScreen.serverFull = Сервер переповнений! disconnectionScreen.noReason = Від’єднано від серверу. disconnectionScreen.invalidSkin = Неправильний формат скіну! disconnectionScreen.invalidName = Неправильний формат ніку! death.fell.accident.generic = {%0} впав з високого місця death.attack.inFire = {%0} згорів death.attack.onFire = {%0} згорів death.attack.lava = {%0} вирішив поплавати в лаві... death.attack.inWall = {%0} задихнувся в стіні death.attack.drown = {%0} потонув death.attack.cactus = {%0} став жертвою кактуса ;-) death.attack.generic = {%0} помер death.attack.explosion = {%0} вибухнув death.attack.explosion.player = {%0} був підірваний {%1} death.attack.magic = {%0} вбитий магією death.attack.wither = {%0} висушений death.attack.mob = {%0} був вбитий {%1} death.attack.player = {%0} був вбитий {%1} death.attack.player.item = {%0} був вбитий {%1} з {%2} death.attack.arrow = {%0} був застрелений {%1} death.attack.arrow.item = {%0} був застрелений {%1} з {%2} death.attack.fall = {%0} впав з високого місця death.attack.outOfWorld = {%0} випав зі світу gameMode.survival = Режим виживання gameMode.creative = Творчий режим gameMode.adventure = Пригодницький режим gameMode.spectator = Режим спостерігача gameMode.changed = Ваш ігровий режим було оновлено potion.moveSpeed = Швидкість potion.moveSlowdown = Уповільнення potion.digSpeed = Поспіх potion.digSlowDown = Втома potion.damageBoost = Сила potion.heal = Миттєве Лікування potion.harm = Миттєва Втрата potion.jump = Посилення Стрибка potion.confusion = Нудота potion.regeneration = Регенерація potion.resistance = Супротив Втратам potion.fireResistance = Вогнестійкість potion.waterBreathing = Підводне Дихання potion.invisibility = Невидимість potion.blindness = Сліпота potion.nightVision = Нічний Зір potion.hunger = Голод potion.weakness = Слабкість potion.poison = Отруєння potion.wither = Висушення potion.healthBoost = Підвищення Здоров’я potion.absorption = Поглинання potion.saturation = Насичення commands.generic.exception = Увага! Невідома помилка при виконанні команди commands.generic.permission = У Вас недостатньо прав для використання цієї команди commands.generic.notFound = Невідома команда. Використовуйте /help для перегляду списку команд commands.generic.player.notFound = Гравця не знайдено commands.generic.usage = Використання: {%0} commands.generic.level = Назва світу commands.generic.seed = Назва сіду commands.generic.name = нік commands.generic.generator = Назва генератору commands.generic.opt.missing = Необхідні налаштування відсутні,будь ласка, повторіть введення. commands.generic.runingame = Використовуйте цю команду тільки в грі. commands.time.added = Додано {%0} до часу commands.time.set = Встановлено час - {%0} commands.time.query = Час ‒ {%0} commands.me.usage = /me <дія> commands.give.item.notFound = Предмет з іменем {%0} не знайдено commands.give.success = Дано {%0} Х {%1} гравцю {%2} commands.give.tagError = Розбір тегу данных завершився невдало: {%0} commands.effect.usage = /effect <гравець> <ефект> [к-сть секунд] [рівень] [прибратиЧастинки] АБО /effect <гравець> clear commands.effect.notFound = Не існує ефекту з ID {%0} commands.effect.success = Дано {%0} (ID {%1}) Х {%2} гравцю {%3} на {%4} секунд commands.effect.success.removed = Ефект {%0} забрано у {%1} commands.effect.success.removed.all = Всі ефекти були зняті з {%0} commands.effect.failure.notActive = Неможливо прибрати ефект {%0} у {%1}, поскільки гравець не має цього ефекту commands.effect.failure.notActive.all = Неможливо прибрати ефекти у {%0}, поскільки гравець взагалі без ефектів commands.enchant.maxLevel = Діапазон рівнів зачарування: 1 - {%0} commands.enchant.noItem = У гравця немає такого предмету commands.enchant.notFound = Немає такого зачарування з ID {%0} commands.enchant.success = Зачарування завершилося вдало commands.enchant.cantEnchant = Цей предмет неможливо зачарувати commands.enchant.usage = /enchant <гравець> [рівень] commands.particle.success = Відтворюються частинки ефекту {%0} {%1} разів commands.particle.notFound = Невідомий ефект {%0} commands.players.usage = /list commands.players.list = Зараз {%0}/{%1} гравців на сервері: commands.kill.successful = Вбито {%0} commands.banlist.ips = Всього заблоковано %d IP адрес: commands.banlist.players = Всього заблоковано {%0} гравців: commands.banlist.cids = Всього заблоковано {%0} номерів пристроїв: commands.banlist.usage = /banlist [ips|players] commands.defaultgamemode.usage = /defaultgamemode <режим гри> commands.defaultgamemode.success = Ігровой режим світу за замовчуванням ‒ {%0} commands.op.success = {%0} тепер оператор серверу commands.op.usage = /op <гравець> commands.deop.success = {%0} вже не оператор серверу commands.deop.usage = /deop <гравець> commands.say.usage = /say <повідомлення> commands.seed.usage = /seed commands.seed.success = Сід: {%0} commands.bancidbyname.success = Гравця {%0} заблоковано по номеру пристрою commands.bancidbyname.usage = /bancidbyname <нік гравця> commands.bancid.success = Заблоковано номер пристрою: {%0} commands.bancid.usage = /bancid <номер пристрою> commands.unbancid.usage = /pardoncid <номер пристрою> commands.unbancid.success = Розблоковано номер пристрою {%0} commands.ban.success = Заблоковано гравця {%0} commands.ban.usage = /ban <нік гравця> [причина] [час(в днях)] commands.unban.success = Розблоковано гравця {%0} commands.unban.usage = /pardon <нік> commands.banip.invalid = Ви ввели неправильну IP-адресу чи даний гравець не в мережі commands.banip.success = Заблоковано IP адресу {%0} commands.banip.success.players = Заблоковано IP адресу {%0}, що належав {%1} commands.banip.usage = /ban-ip [причина] commands.unbanip.invalid = Ви ввели неправильну IP адресу commands.unbanip.success = Розблоковано IP адресу {%0} commands.unbanip.usage = /pardon-ip <адреса> commands.banipbyname.success = Гравця з ніком {%0} заблоковано по IP commands.banipbyname.usage = /banipbyname <гравець> commands.fill.usage = /fill commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = Ввімкнено автоматичне збереження світу commands.save.disabled = Автоматичне збереження світу було вимкнено commands.save.start = Збереження світу... commands.save.success = Світ збережено commands.setblock.usage = /setblock <назва блоку> [втрата] command.setblock.invalidBlock = Недопустима назва блоку чи ID commands.stop.usage = /stop commands.stop.start = Зупинка серверу commands.kick.success = {%0} було від’єднано від серверу commands.kick.success.reason = Гравця {%0} було від’єднано від серверу. Причина: '{%1}' commands.kick.usage = /kick <гравець> [причина] commands.tp.success = Переміщено {%0} до {%1} commands.tp.success.coordinates = Переміщено {%0} на координати {%1}, {%2}, {%3} commands.tp.usage = /tp [цільовий гравець] <назначений гравець> або /tp [цільовий гравець] [ ] commands.whitelist.list = Білий список має {%0} гравців (з {%1} відображених): commands.whitelist.enabled = Білий список ввімкнено commands.whitelist.disabled = Білий список вимкнено commands.whitelist.reloaded = Білий список перезавантажено commands.whitelist.add.success = {%0} додано до білого списку commands.whitelist.add.usage = /whitelist add <гравець> commands.whitelist.remove.success = {%0} видалено з білого списку commands.whitelist.remove.usage = /whitelist remove <гравець> commands.whitelist.usage = /whitelist commands.gamemode.success.self = Встановлено власний режим гри - {%2} commands.gamemode.success.other = Режим гри для гравця {%0} змінено на {%1} commands.gamemode.usage = /gamemode <режим гри> [гравець] commands.help.header = --- Строінка {%0} з {%1} (/help <сторінка>) --- commands.help.usage = /help [сторінка|назва команди] commands.message.usage = /tell <гравець> <приватне повідомлення> commands.message.sameTarget = Нащо самому собі писати приватне повідомлення?! commands.difficulty.usage = /difficulty <складність гри> commands.difficulty.success = Складність гри змінено на {%0} commands.spawnpoint.usage = /spawnpoint [гравець] [ ] commands.spawnpoint.success = Місце появи гравця {%0} - ({%1}, {%2}, {%3}) commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = Місце появи у світі - ({%0}, {%1}, {%2}) commands.summon.usage = /summon [сутність] [ ] [назва] commands.xp.failure.withdrawXp = Неможливо дати гравцю від’ємне значення досвіду commands.xp.success = Дано {%0} досвіду гравцю {%1} commands.xp.success.levels = Дано {%0} рівнів досвіду гравцю {%1} commands.xp.success.negative.levels = Забрано {%0} рівнів досвіду від гравця {%1} commands.xp.usage = /xp <кількість> [гравець] або /xp <кількість рівнів> [гравець] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = Інформацію про гравця "{%0}" не знайдено, створюємо новий профіль pocketmine.data.playerCorrupted = Дані гравця "{%0}" пошкоджено, створюємо новий профіль pocketmine.data.playerOld = Профіль гравця "{%0}" застарілий, оновлюємо на новий pocketmine.data.saveError = Помилка збереження профіля гравця "{%0}": {%1} pocketmine.level.notFound = Світ "{%0}" не знайдено pocketmine.level.loadError = Помилка завантаження світу "{%0}": {%1} pocketmine.level.generationError = Помилка генерації світу "{%0}": {%1} pocketmine.level.tickError = Помилка промальовки світу "{%0}": {%1} pocketmine.level.chunkUnloadError = Помилка під час вивантаження чанку: {%0} pocketmine.level.backgroundGeneration = Генерується місце появи для світу "{%0}" pocketmine.level.defaultError = Помилка завантаження світу за замовчуванням pocketmine.level.preparing = Підготовка світу "{%0}" pocketmine.level.unloading = Вивантаження світу "{%0}" pocketmine.server.start = Запускається сервер Minecraft PE {%0} pocketmine.server.networkError = [Мережа] Зупинено інтерфейс {%0}. Причина: {%1} pocketmine.server.networkStart = Реєстрація серверу на {%0}:{%1} pocketmine.server.info.extended = Сервер використовує {%0} {%1} 「{%2}」. Версія API: {%3}. Версія Minecraft PE: {%4}. Протокол версії {%5} pocketmine.server.info = Цей сервер використовує {%0}, версію {%1} "{%2}" (API {%3}) pocketmine.server.license = {%0} розповсюджується за ліцензією LGPL pocketmine.server.tickOverload = Сервер перевантажено! pocketmine.server.startFinished = Завантаження виконано. Час: {%0} секунд. Довідка по командам - "help" або "?" pocketmine.server.defaultGameMode = Режим гри за замовчуванням: {%0} pocketmine.server.query.start = Запуск прослуховувача GS4 pocketmine.server.query.info = QUERY порт - {%0} pocketmine.server.query.running = Реєстрація QUERY на {%0}:{%1} pocketmine.command.alias.illegal = Введено недопустимі знаки для альтернативи команди {%0} pocketmine.command.alias.notFound = Помилка реєстрації альтернативи команди {%0}: знайдено недопустимі команди - {%1} pocketmine.command.exception = Необроблене виключення при виконанні команди '{%0}' в {%1}: {%2} pocketmine.commands.cave.usage = /cave <кут повороту> <довжина> <номер вітки> <міцність> <назва світу> | /cave getmypos pocketmine.commands.cave.info = Кут повороту: {%0}. Довжина: {%1}. Номер вітки: {%2}. Міцність: {%3}. pocketmine.commands.cave.start = Створення печери розпочато! Будь ласка, зачекайте. pocketmine.command.plugins.description = Список плагінів серверу pocketmine.command.plugins.success = Плагіни ({%0}): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Перезавантажити налаштування серверу и плагіни pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Перезавантаження серверу... pocketmine.command.reload.reloaded = Перезавантаження виконано. pocketmine.command.lvdat.description = Змінити налаштування мапи. pocketmine.command.lvdat.changed = Змінено параметр {%1} для світу "{%0}", необхідне перезавантаження для активації змін. pocketmine.command.lvdat.fixname = Назву світу "{%0}" було успішно відредаговано, необхідне перезавантаження для активації змін. pocketmine.command.lvdat.nofound = Світ "{%0}" не знайдено або не завантажено. pocketmine.command.lvdat.preset = Налаштування генератору (пресети) pocketmine.command.status.description = Інформація про продуктивність серверу. pocketmine.command.status.usage = /status pocketmine.command.status.title = Статус серверу pocketmine.command.status.player = Всього гравців: pocketmine.command.status.days = днів pocketmine.command.status.hours = годин pocketmine.command.status.minutes = хвилин pocketmine.command.status.seconds = секунд pocketmine.command.status.uptime = Час роботи серверу: pocketmine.command.status.AverageTPS = Середній TPS: pocketmine.command.status.CurrentTPS = Поточний TPS: pocketmine.command.status.Networkupload = Даних відправлено: pocketmine.command.status.Networkdownload = Даних отримано: pocketmine.command.status.Threadcount = Кількість потоків: pocketmine.command.status.Mainmemory = Основна пам’ять потоку: pocketmine.command.status.Totalmemory = Загальна пам’ять: pocketmine.command.status.Totalvirtualmemory = Загальна віртуальна пам’ять: pocketmine.command.status.Heapmemory = Пам’ять кучі (Java Heap Memory): pocketmine.command.status.Maxmemorysystem = Максимальна пам’ять системи: pocketmine.command.status.Maxmemorymanager = Максимальна пам’ять менеджера: pocketmine.command.status.World = Світ pocketmine.command.status.chunks = чанків, pocketmine.command.status.entities = сутностей, pocketmine.command.status.tiles = тайлів. pocketmine.command.status.Time = Час pocketmine.command.status.ms = мс pocketmine.command.gc.description = Запускає процес збирання сміття pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = Результат очистки pocketmine.command.gc.chunks = Чанків: pocketmine.command.gc.entities = Сутностей: pocketmine.command.gc.tiles = Тайлів: pocketmine.command.gc.cycles = Циклів: pocketmine.command.gc.memory = Звільнена пам’ять: pocketmine.command.biome.description = Змінює біом обраної області. pocketmine.command.biome.posset = Встановлено точку {%3} на позиції: ({%1},{%2})[{%0}] pocketmine.command.biome.get = ID біома, де Ви знаходитесь: {%0}. Колір: {%1},{%2},{%3} pocketmine.command.biome.wrongLev = Точки знаходяться в різних світах. pocketmine.command.biome.wrongBio = Помилковий ID біому. Приклад біомів: 1 (Рівнини), 2 (Пустеля),13 (Льодяні гори),6 (Болото) pocketmine.command.biome.wrongCol = Неправильний колір! Приклад: 146,188,89. Використовуйте: "/biome get" для отримання решти кольорів. pocketmine.command.biome.noPos = Використовуйте "/biome pos1|pos2", щоб встановити точки. pocketmine.command.biome.set = Колір було змінено на {%0} pocketmine.command.biome.color = Обрано колір {%0},{%1},{%2} pocketmine.command.timings.description = Фіксує інформацію про середню продуктивність серверу. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Розпочато фіксування даних про продуктивність серверу. pocketmine.command.timings.disable = Фіксування даних про продуктивність серверу припинено. pocketmine.command.timings.timingsDisabled = Для повторної активації фіксування даних про продуктивність серверу використовуйте /timings on pocketmine.command.timings.reset = Дані про продуктивність серверу було скинуто pocketmine.command.timings.pasteError = Помилка при фіксуванні даних про продуктивність серверу pocketmine.command.timings.timingsUpload = Дані про продуктивність серверу завантажені з {%0} pocketmine.command.timings.timingsRead = Дані про продуктивність серверу Ви можете отримати тут: {%0} pocketmine.command.timings.timingsWrite = Дані про продуктивність серверу збережені до {%0} pocketmine.command.version.description = Інформація про версію серверу і про встановлені плагіни pocketmine.command.version.usage = /version [назва плагіну] pocketmine.command.version.noSuchPlugin = Плагіна з такою назвою не знайдено. Використовуйте /plugins для отримання списку плагінів. pocketmine.command.give.description = Дає гравцю зазначену к-сть предмету pocketmine.command.give.usage = /give <гравець> <предмет[:втрата]> [кількість] [теги] pocketmine.command.kill.description = Скоєння самогубства або вбивство іншого гравця pocketmine.command.kill.usage = /kill [гравець] pocketmine.command.particle.description = Додавання частинок у світ pocketmine.command.particle.usage = /particle <назва> [значення] [дані] pocketmine.command.time.description = Перегляд або глобальна зміна часу pocketmine.command.time.usage = /time <значення> або /time pocketmine.command.bancidbyname.description = Блокування гравця по номеру пристрою pocketmine.command.bancid.description = Блокування номера пристрою pocketmine.command.banipbyname.description = Блокування гравця по IP pocketmine.command.ban.player.description = Блокування гравця pocketmine.command.ban.ip.description = Блокування IP адреси pocketmine.command.banlist.description = Список заблокованих гравців pocketmine.command.defaultgamemode.description = Встановлює режим гри за замовчуванням pocketmine.command.deop.description = Знімає статус оператора з гравця pocketmine.command.difficulty.description = Встановлює складність гри pocketmine.command.enchant.description = Додає зачарування предметам pocketmine.command.effect.description = Зміна ефектів у гравців pocketmine.command.gamemode.description = Зміна режиму гри у гравця pocketmine.command.help.description = Довідка pocketmine.command.kick.description = Примусове від’єднання гравця від серверу pocketmine.command.list.description = Список гравців серверу, що зараз грають pocketmine.command.me.description = Виконує вказану дію в чаті pocketmine.command.op.description = Встановлює статус оператора для гравця pocketmine.command.unban.cid.description = Розблокування номера пристрою pocketmine.command.unban.player.description = Розблокування гравця по ніку pocketmine.command.unban.ip.description = Розблокування IP адреси pocketmine.command.save.description = Збереження файлів серверу pocketmine.command.saveoff.description = Вимикання автоматичного збереження pocketmine.command.saveon.description = Вмикання автоматичного збереження pocketmine.command.say.description = Написати в чат від імені серверу pocketmine.command.seed.description = Поточний сід світу pocketmine.command.setworldspawn.description = Встановлення місце появи гравців у світі. pocketmine.command.spawnpoint.description = Встановлення місце появи окремого гравця у світі pocketmine.command.stop.description = Вимикання серверу pocketmine.command.tp.description = Переміщення гравців pocketmine.command.tell.description = Надсилання приватного повідомлення гравцю pocketmine.command.xp.description = Зміна досвіду в гравця pocketmine.command.summon.description = Створення сутності pocketmine.command.fill.description = Заповнює вибрану область блоками pocketmine.command.fill.missingParameter = Пропущені параметри pocketmine.command.fill.usage = /fill pocketmine.command.setblock.description = Змінює блок по відповідних координатах pocketmine.command.weather.description = Зміна погоди у світі pocketmine.command.weather.usage = /weather <світ> (0,1,2,3) pocketmine.command.weather.changed = Успішно змінено погоду в світі {%0}! pocketmine.command.weather.noregistered = Світ {%0} не було зареєстровано в менеджері погоди. pocketmine.command.weather.invalid = Помилкове значення погоди. pocketmine.command.weather.wrong = Некорректні параметри. pocketmine.command.weather.invalid.level = Неправильна назва світу. pocketmine.command.whitelist.description = Керування білим списком серверу pocketmine.crash.create = Увага! Роботу серверу припинено через фатальну помилку. Створення аварійного дампу. pocketmine.crash.error = Помилка створення дампу: {%0} pocketmine.crash.submit = Буль ласка, завантажте файл "{%0}" до Crash Archive і надішліть посилання на сторінку Bug Reporting. Вкажіть якомога більше інформації про цю помилку. pocketmine.crash.archive = Аварійний дамп було автоматично відправлено до Crash Archive. Ви можете подивитися його тут: {%0}, або використати ID #{%1}. pocketmine.debug.enable = Підтримку LevelDB ввімкнено pocketmine.player.invalidMove = {%0} некорректно пересунувся! pocketmine.player.logIn = {%0}[/{%1}:{%2}] приєднався з ID сутності {%3} на ({%4}, {%5}, {%6}, {%7}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] від’єднався: {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}] було переміщено до {%3} pocketmine.player.invalidEntity = {%0} спробував атакувати некорректну сутність pocketmine.plugin.load = Завантаження {%0} pocketmine.plugin.enable = Ввімкнення {%0} pocketmine.plugin.disable = Вимкнення {%0} pocketmine.plugin.restrictedName = Обмежене ім’я pocketmine.plugin.incompatibleAPI = Несумісна версія API pocketmine.plugin.unknownDependency = Невідома залежність pocketmine.plugin.circularDependency = Виявлена кругова залежність pocketmine.plugin.genericLoadError = Помилка завантаження плагіну '{%0}' pocketmine.plugin.spacesDiscouraged = Назва плагіну '{%0}' вказана з використанням пробілів, що є недопустимим pocketmine.plugin.loadError = Помилка завантаження плагіну '{%0}': {%1} pocketmine.plugin.duplicateError = Помилка завантаження '{%0}': плагін вже існує pocketmine.plugin.fileError = Помилка завантаження '{%0}' в папці '{%1}': {%2} pocketmine.plugin.commandError = Помилка завантаження команди {%0} для плагіну {%1} pocketmine.plugin.aliasError = Помилка завантаження альтернативу {%0} для плагіну {%1} pocketmine.plugin.deprecatedEvent = Плагін '{%0}' зареєстрував прослуховувача для '{%1}', використовуючи метод '{%2}'. Ця подія є застарілою. pocketmine.plugin.eventError = "Помилка обробки події '{%0}' в '{%1}': {%2} в {%3}" # Language file compatible with Minecraft: Pocket Edition identifiers # # A message doesn't need to be there to be shown correctly on the client. # Only messages shown in PocketMine itself need to be here language.name = Vietnamese language.selected = Chọn {%0} ({%1}) là ngôn ngữ chính multiplayer.player.joined = {%0} tham gia server multiplayer.player.left = {%0} rời khỏi server chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0} vừa nhận được danh hiệu {%1} disconnectionScreen.outdatedClient = Phiên bản không hợp lệ! disconnectionScreen.outdatedServer = Server có phiên bản không hợp lệ! disconnectionScreen.serverFull = Server đã đầy! disconnectionScreen.noReason = Mất kết nối tới server disconnectionScreen.invalidSkin = Skin không hợp lệ! disconnectionScreen.invalidName = Tên không hợp lệ! death.fell.accident.generic = {%0} ngã từ nơi rất cao! death.attack.inFire = {%0} đi vào đám cháy! death.attack.onFire = {%0} bỏng tới chết! death.attack.lava = {%0} thử bơi vào dung nham death.attack.inWall = {%0} kẹt tại bức tường death.attack.drown = {%0} chết chìm death.attack.cactus = {%0} bị đâm tới chết death.attack.generic = {%0} chết death.attack.explosion = {%0} bị bùng nổ death.attack.explosion.player = {%0} bị nổ bởi {%1} death.attack.magic = {%0} bị giết bởi phép thuật death.attack.wither = {%0} hóa xương death.attack.mob = {%0} bị giết bởi {%1} death.attack.player = {%0} bị giết bởi {%1} death.attack.player.item = {%0} bị giết bởi {%1} sử dụng {%2} death.attack.arrow = {%0} bị bắn bởi {%1} death.attack.arrow.item = {%0} bị bắn bởi {%1} sử dụng {%2} death.attack.fall = {%0} rơi xuống đất quá nặng death.attack.outOfWorld = {%0} rơi khỏi thế giới gameMode.survival = Chế độ sống sốt gameMode.creative = Chế độ sáng tạo gameMode.adventure = Chế độ phiêu lưu gameMode.spectator = Chế độ theo dõi gameMode.changed = Chế độ chơi của bạn đã được cập nhật potion.moveSpeed = Nhanh nhẹn potion.moveSlowdown = Chậm chạp potion.digSpeed = Sức đào nhanh potion.digSlowDown = Sức đào chậm potion.damageBoost = Khỏe mạnh potion.heal = Hồi máu tức thì potion.harm = Sát thương tức thì potion.jump = Nhảy cao potion.confusion = Choáng váng potion.regeneration = Hồi máu mỗi giây potion.resistance = Kháng sát thương potion.fireResistance = Kháng lửa potion.waterBreathing = Thở dưới nước potion.invisibility = Tàng hình potion.blindness = Mù potion.nightVision = Mắt đại bàng potion.hunger = Đói potion.weakness = Yếu potion.poison = Độc potion.wither = Xương potion.healthBoost = Thêm máu potion.absorption = Khiên máu potion.saturation = Hút máu commands.generic.exception = Lỗi khi thưc hiện câu lệnh này commands.generic.permission = Bạn không có quyền sử dụng câu lệnh này commands.generic.notFound = Sai cú pháp. Nhấn /help để xem các câu lệnh commands.generic.player.notFound = Không tìm thấy người chơi này commands.generic.usage = Cú pháp: {%0} commands.generic.level = Tên-level commands.generic.seed = Tên-seed commands.generic.name = Tên commands.generic.generator = Tên-tạo commands.generic.opt.missing = Thiếu phần sửa được yêu cầu,vui lòng xác nhận và đánh lại. commands.generic.runingame = Vui lòng chạy câu lệnh trong server. commands.time.added = Thêm {%0} vào thời gian commands.time.set = Đặt thời gian là {%0} commands.time.query = Thời gian là {%0} commands.me.usage = /me commands.give.item.notFound = Không có đồ nào có id {%0} commands.give.success = Đưa {%0} * {%1} cho {%2} commands.give.tagError = Data tag parsing failed: {%0} commands.effect.usage = /effect [giây] [cấp độ] [bổ sung lời nhắn] hoặc /effect clear commands.effect.notFound = Không có dạng hiệu ứng với id {%0} commands.effect.success = Tạo hiệu ứng {%0} (ID {%1}) * {%2} cho {%3} là {%4} seconds commands.effect.success.removed = Lấy hết {%0} từ {%1} commands.effect.success.removed.all = Lấy tất cả hiệu ứng từ {%0} commands.effect.failure.notActive = Không thể lấy {%0} từ {%1} vì họ không có hiệu ứng commands.effect.failure.notActive.all = Không thể lấy tất cả hiệu ứng từ {%0} vì họ không có commands.enchant.noItem = Người chơi không cầm vũ khí commands.enchant.notFound = Không có dạng cường hóa với id {%0} commands.enchant.success = Cường hóa thành công commands.enchant.cantEnchant = This item cannot be enchanted commands.enchant.usage = /enchant [cấp độ] commands.particle.success = Chơi cường hóa {%0} trong thời gian là {%1} commands.particle.notFound = Tên cường hóa không hợp lệ với {%0} commands.players.usage = /list commands.players.list = Có tất cả {%0}/{%1} đang online: commands.kill.successful = Giết {%0} commands.banlist.ips = Có tất cả %d IP bị cấm trong server: commands.banlist.players = Có tất cả {%0} người chơi bị cấm: commands.banlist.usage = /banlist [ips|players] commands.defaultgamemode.usage = /defaultgamemode commands.defaultgamemode.success = Chế độ mặc định của world được đặt là {%0} commands.op.success = Bổ nhiệm {%0} commands.op.usage = /op commands.deop.success = Bãi nhiệm {%0} commands.deop.usage = /deop commands.say.usage = /say commands.seed.usage = /seed commands.seed.success = Hạt giống: {%0} commands.bancidbyname.success = Cấm hệ thống của người chơi {%0} commands.bancidbyname.usage = /bancidbyname commands.bancid.success = Cấm hệ thống: {%0} commands.bancid.usage = /bancid commands.unbancid.usage = /pardoncid commands.ban.success = Cấm người chơi {%0} commands.ban.usage = /ban [lý do ...] [time(day)] commands.unban.success = Hủy cấm người chơi {%0} commands.unban.usage = /pardon commands.banip.invalid = IP không hợp lệ hoặc người chơi chưa online commands.banip.success = Cấm địa chỉ {%0} commands.banip.success.players = Cấm địa chỉ {%0} thuộc về {%1} commands.banip.usage = /ban-ip <địa chỉ|tên người chơi> [lý do ...] commands.unbanip.invalid = Địa chỉ không hợp lệ commands.unbanip.success = Hủy cấm địa chỉ {%0} commands.unbanip.usage = /pardon-ip <địa chỉ> commands.banipbyname.success = Cấm địa chỉ của người chơi {%0} commands.banipbyname.usage = /banipbyname commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = Bật chế độ tự động lưu thế giới commands.save.disabled = Tắt chế độ tự động lưu thế giới commands.save.start = Đang lưu... commands.save.success = Đã lưu toàn bộ! commands.setblock.usage = /setblock [damage] command.setblock.invalidBlock = ID/tên block không hợp lệ commands.stop.usage = /stop commands.stop.start = Đang dừng server... commands.kick.success = Đuổi người chơi {%0} khỏi server commands.kick.success.reason = Đuổi {%0} khỏi server. Lý do: '{%1}' commands.kick.usage = /kick [lý do ...] commands.tp.success = Di chuyển người chơi {%0} đến {%1} commands.tp.success.coordinates = Di chuyển người chơi {%0} đến {%1}, {%2}, {%3} commands.tp.usage = /tp [người chơi 1] OR /tp [người chơi 1] [ ] commands.whitelist.list = Có tất cả {%0} (ngoại trừ {%1}) người chơi trong danh sách trắng: commands.whitelist.enabled = Bật danh sách trắng commands.whitelist.disabled = Tắt danh sách trắng commands.whitelist.reloaded = Tải lại danh sách trắng commands.whitelist.add.success = Thêm {%0} vào danh sách trắng commands.whitelist.add.usage = /whitelist add commands.whitelist.remove.success = Loại {%0} khỏi danh sách trắng commands.whitelist.remove.usage = /whitelist remove commands.whitelist.usage = /whitelist commands.gamemode.success.self = Đặt chế độ chơi là {%2} commands.gamemode.success.other = Đặt {%0} cho {%1} commands.gamemode.usage = /gamemode [người chơi] commands.help.header = ---> Xem trang hướng dẫn {%0} trong {%1} (/help ) --- commands.help.usage = /help [sô trang|câu lệnh] commands.message.usage = /tell commands.message.sameTarget = Bạn không thể gửi cho chính mình! commands.xp.usage = /xp commands.difficulty.usage = /difficulty commands.difficulty.success = Đặt mức độ chơi ở mức {%0} commands.spawnpoint.usage = /spawnpoint [người chơi] [ ] commands.spawnpoint.success = Đặt điểm spawn {%0} ở ({%1}, {%2}, {%3}) commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = Đặt điểm mặc định ở ({%0}, {%1}, {%2}) commands.summon.usage = /summon [tên mob] [ ] [đặt tên cho mob] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = Player data not found for "{%0}", creating new profile pocketmine.data.playerCorrupted = Corrupted data found for "{%0}", creating new profile pocketmine.data.playerOld = Old Player data found for "{%0}", upgrading profile pocketmine.data.saveError = Could not save player "{%0}": {%1} pocketmine.level.notFound = Level "{%0}" not found pocketmine.level.loadError = Could not load level "{%0}": {%1} pocketmine.level.generationError = Could not generate level "{%0}": {%1} pocketmine.level.tickError = Could not tick level "{%0}": {%1} pocketmine.level.chunkUnloadError = Error while unloading a chunk: {%0} pocketmine.level.backgroundGeneration = Spawn terrain for level "{%0}" is being generated in the background pocketmine.level.defaultError = No default level has been loaded pocketmine.level.preparing = Preparing level "{%0}" pocketmine.level.unloading = Unloading level "{%0}" pocketmine.server.start = Starting Minecraft: PE server version {%0} pocketmine.server.networkError = [Network] Stopped interface {%0} due to {%1} pocketmine.server.networkStart = Opening server on {%0}:{%1} pocketmine.server.info = This server is running {%0} version {%1} "{%2}" (API {%3}) pocketmine.server.info.extended = This server is running {%0} {%1} 「{%2}」 implementing API version {%3} for Minecraft: PE {%4} (protocol version {%5}) pocketmine.server.license = {%0} is distributed under the LGPL License pocketmine.server.tickOverload = Can't keep up! Is the server overloaded? pocketmine.server.startFinished = Done ({%0}s)! For help, type "help" or "?" pocketmine.server.defaultGameMode = Default game type: {%0} pocketmine.server.query.start = Starting GS4 status listener pocketmine.server.query.info = Setting query port to {%0} pocketmine.server.query.running = Query running on {%0}:{%1} pocketmine.command.alias.illegal = Could not register alias {%0} because it contains illegal characters pocketmine.command.alias.notFound = Could not register alias {%0} because it contains commands that do not exist: {%1} pocketmine.command.exception = Unhandled exception executing command '{%0}' in {%1}: {%2} pocketmine.commands.cave.usage = /cave | /cave getmypos pocketmine.commands.cave.info = Angle of rotation:{%0} Length:{%1} Branch Number:{%2} Strength:{%3} pocketmine.commands.cave.start = Start to generate cave,please wait pocketmine.command.plugins.description = Gets a list of plugins running on the server pocketmine.command.plugins.success = Plugins ({%0}): {%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = Reloads the server configuration and plugins pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = Reloading server... pocketmine.command.reload.reloaded = Reload complete. pocketmine.command.lvdat.description = Change properties of a map. pocketmine.command.lvdat.changed = has changed {%1} of level "{%0}", some change need to reboot your server. pocketmine.command.lvdat.fixname = fixname successfully for level "{%0}", some change need to reboot your server. pocketmine.command.lvdat.nofound = level "{%0}" no found or load failed. pocketmine.command.lvdat.preset = Generator setting (preset) pocketmine.command.status.description = Reads back the server's performance. pocketmine.command.status.usage = /status pocketmine.command.status.title = Server status pocketmine.command.status.player = Player count: pocketmine.command.status.days = days pocketmine.command.status.hours = hours pocketmine.command.status.minutes = minutes pocketmine.command.status.seconds = seconds pocketmine.command.status.uptime = Uptime: pocketmine.command.status.AverageTPS = Average TPS: pocketmine.command.status.CurrentTPS = Current TPS: pocketmine.command.status.Networkupload = Network upload: pocketmine.command.status.Networkdownload = Network download: pocketmine.command.status.Threadcount = Thread count: pocketmine.command.status.Mainmemory = Main thread memory: pocketmine.command.status.Totalmemory = Total memory: pocketmine.command.status.Totalvirtualmemory = Total virtual memory: pocketmine.command.status.Heapmemory = Heap memory: pocketmine.command.status.Maxmemorysystem = Maximum memory (system): pocketmine.command.status.Maxmemorymanager = Maximum memory (manager): pocketmine.command.status.World = World pocketmine.command.status.chunks = chunks, pocketmine.command.status.entities = entities, pocketmine.command.status.tiles = tiles. pocketmine.command.status.Time = Time pocketmine.command.status.ms = ms pocketmine.command.gc.description = Fires garbage collection tasks pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = Danh sách dọn dẹp: pocketmine.command.gc.chunks = Chunks: pocketmine.command.gc.entities = Vật thể: pocketmine.command.gc.tiles = Tiles: pocketmine.command.gc.cycles = Cycles: pocketmine.command.gc.memory = Bộ nhớ được thả: pocketmine.command.biome.description = change the biome of the area.(To change snow or rain) pocketmine.command.biome.posset = Has set pos{%3} at:({%1},{%2})[{%0}] pocketmine.command.biome.get = The ID of biome you stay is {%0} Color:{%1},{%2},{%3} pocketmine.command.biome.wrongLev = Cannot set point in different level. pocketmine.command.biome.wrongBio = Wrong ID of biome. e.g. 1 (Plains), 2 (Desert),13 (Ice Mountains),6 (Swampland) pocketmine.command.biome.wrongCol = Wrong Color. e.g. 146,188,89 .Use "/biome get" to get other color. pocketmine.command.biome.noPos = Plz use "/biome pos1|pos2" to select the area first. pocketmine.command.biome.set = Has set biome as:{%0} pocketmine.command.biome.color = Has set color as:{%0},{%1},{%2} pocketmine.command.timings.description = Records timings to see performance of the server. pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = Enabled Timings & Reset pocketmine.command.timings.disable = Disabled Timings pocketmine.command.timings.timingsDisabled = Please enable timings by typing /timings on pocketmine.command.timings.reset = Timings reset pocketmine.command.timings.pasteError = An error happened while pasting the report pocketmine.command.timings.timingsUpload = Timings uploaded to {%0} pocketmine.command.timings.timingsRead = You can read the results at {%0} pocketmine.command.timings.timingsWrite = Timings written to {%0} pocketmine.command.version.description = Gets the version of this server including any plugins in use pocketmine.command.version.usage = /version [plugin name] pocketmine.command.version.noSuchPlugin = This server is not running any plugin by that name. Use /plugins to get a list of plugins. pocketmine.command.give.description = Gives the specified player a certain amount of items pocketmine.command.give.usage = /give [amount] [tags...] pocketmine.command.kill.description = Commit suicide or kill other players pocketmine.command.kill.usage = /kill [player] pocketmine.command.particle.description = Adds particles to a world pocketmine.command.particle.usage = /particle [count] [data] pocketmine.command.time.description = Changes the time on each world pocketmine.command.time.usage = /time OR /time pocketmine.command.bancidbyname.description = Prevents the specified CID by name from using this server禁止指定玩家的設備 ID pocketmine.command.bancid.description = Prevents the specified CID from using this server pocketmine.command.banipbyname.description = Prevents the specified IP address by name from using this server pocketmine.command.ban.player.description = Prevents the specified player from using this server pocketmine.command.ban.ip.description = Prevents the specified IP address from using this server pocketmine.command.banlist.description = View all players banned from this server pocketmine.command.defaultgamemode.description = Set the default gamemode pocketmine.command.deop.description = Takes the specified player's operator status pocketmine.command.difficulty.description = Sets the game difficulty pocketmine.command.enchant.description = Adds enchantments on items pocketmine.command.effect.description = Adds/Removes effects on players pocketmine.command.gamemode.description = Changes the player to a specific game mode pocketmine.command.help.description = Shows the help menu pocketmine.command.kick.description = Removes the specified player from the server pocketmine.command.list.description = Lists all online players pocketmine.command.me.description = Performs the specified action in chat pocketmine.command.op.description = Gives the specified player operator status pocketmine.command.unban.cid.description = Allows the specified CID to use this server pocketmine.command.unban.player.description = Allows the specified player to use this server pocketmine.command.unban.ip.description = Allows the specified IP address to use this server pocketmine.command.save.description = Saves the server to disk pocketmine.command.saveoff.description = Disables server autosaving pocketmine.command.saveon.description = Enables server autosaving pocketmine.command.say.description = Broadcasts the given message as the sender pocketmine.command.seed.description = Shows the world seed pocketmine.command.setworldspawn.description = Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. pocketmine.command.spawnpoint.description = Sets a player's spawn point pocketmine.command.stop.description = Stops the server pocketmine.command.tp.description = Teleports the given player (or yourself) to another player or coordinates pocketmine.command.tell.description = Sends a private message to the given player pocketmine.command.xp.description = Add experience or experience level to the given player pocketmine.command.summon.description = Summons a entity at the player's location or a specific location pocketmine.command.fill.description = fills a specific selection with blocks pocketmine.command.setblock.description = Changes a block to another block pocketmine.command.weather.description = Set weather for level pocketmine.command.weather.usage = /weather pocketmine.command.weather.changed = Weather changed successfully in level {%0}! pocketmine.command.weather.noregistered = level {%0} hasn't registered to WeatherManager. pocketmine.command.weather.invalid = Invalid weather.(0,1,2,3) pocketmine.command.weather.wrong = Wrong parameters. pocketmine.command.weather.invalid.level = Invalid level name. pocketmine.command.whitelist.description = Manages the list of players allowed to use this server pocketmine.crash.create = An unrecoverable error has occurred and the server has crashed. Creating a crash dump pocketmine.crash.error = Could not create crash dump: {%0} pocketmine.crash.submit = Please upload the "{%0}" file to the Crash Archive and submit the link to the Bug Reporting page. Give as much info as you can. pocketmine.crash.archive = The crash dump has been automatically submitted to the Crash Archive. You can view it on {%0} or use the ID #{%1}. pocketmine.debug.enable = LevelDB support enabled pocketmine.player.invalidMove = {%0} moved wrongly! pocketmine.player.logIn = {%0}[/{%1}:{%2}] [ClientID: {%3}] logged in with entity id {%4} at ({%5}, {%6}, {%7}, {%8}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] logged out due to {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}] was transferred to {%3} pocketmine.player.invalidEntity = {%0} tried to attack an invalid entity pocketmine.plugin.load = Loading {%0} pocketmine.plugin.enable = Enabling {%0} pocketmine.plugin.disable = Disabling {%0} pocketmine.plugin.restrictedName = Restricted name pocketmine.plugin.incompatibleAPI = Incompatible API version pocketmine.plugin.unknownDependency = Unknown dependency pocketmine.plugin.circularDependency = Circular dependency detected pocketmine.plugin.genericLoadError = Could not load plugin '{%0}' pocketmine.plugin.spacesDiscouraged = Plugin '{%0}' uses spaces in its name, this is discouraged pocketmine.plugin.loadError = Could not load plugin '{%0}': {%1} pocketmine.plugin.duplicateError = Could not load plugin '{%0}': plugin exists pocketmine.plugin.fileError = Could not load '{%0}' in folder '{%1}': {%2} pocketmine.plugin.commandError = Could not load command {%0} for plugin {%1} pocketmine.plugin.aliasError = Could not load alias {%0} for plugin {%1} pocketmine.plugin.deprecatedEvent = Plugin '{%0}' has registered a listener for '{%1}' on method '{%2}', but the event is Deprecated. pocketmine.plugin.eventError = "Could not pass event '{%0}' to '{%1}': {%2} on {%3} # Language file compatible with Minecraft: Pocket Edition identifiers= #= # A message doesn't need to be there to be shown correctly on the client.= # Only messages shown in PocketMine itself need to be here= language.name = 中文(繁體) language.selected = 設定 {%0} ({%1}) 為基本語言 multiplayer.player.joined = {%0} 加入了伺服器 multiplayer.player.left = {%0} 離開了伺服器 chat.type.text = <{%0}> {%1} chat.type.emote = * {%0} {%1} chat.type.announcement = [{%0}] {%1} chat.type.admin = [{%0}: {%1}] chat.type.achievement = {%0} 剛剛獲得了成就 {%1} disconnectionScreen.notAuthenticated = 您需要 Xbox 登入 disconnectionScreen.outdatedClient = 用戶端版本過舊! disconnectionScreen.outdatedServer = 伺服器版本過舊! disconnectionScreen.serverFull = 伺服器人數已滿! disconnectionScreen.noReason = 與伺服器連線中斷 disconnectionScreen.invalidSkin = 無效的皮膚! disconnectionScreen.invalidName = 無效名稱! disconnectionScreen.refusedResourcePack = 必須下載資源包才能加入伺服器! disconnectionScreen.unavailableResourcePack = 無法在伺服器上找到所需的資源包! death.fell.accident.generic = {%0} 從高處摔落 death.attack.inFire = {%0} 在火焰中升天 death.attack.onFire = {%0} 被燒死了 death.attack.lava = {%0} 嘗試在熔岩游泳 death.attack.inWall = {%0} 在牆壁裡窒息 death.attack.drown = {%0} 溺死了 death.attack.cactus = {%0} 被仙人掌刺死了 death.attack.generic = {%0} 已死亡 death.attack.explosion = {%0} 被炸飛了 death.attack.explosion.player = {%0} 被 {%1} 炸死了 death.attack.magic = {%0} 被魔法殺死了 death.attack.wither = {%0} 凋零至死了 death.attack.mob = {%0} 被 {%1} 殺死了 death.attack.player = {%0} 被 {%1} 殺死了 death.attack.player.item = {%0} 被 {%1} 用 {%2} 殺死了 death.attack.arrow = {%0} 被 {%1} 射殺了 death.attack.arrow.item = {%0} 被 {%1} 用 {%2} 射殺了 death.attack.fall = {%0} 以為能安然無恙的著地 death.attack.outOfWorld = {%0} 掉到世界外面了 gameMode.survival = 生存模式 gameMode.creative = 創造模式 gameMode.adventure = 冒險模式 gameMode.spectator = 觀眾模式 gameMode.changed = 您的遊戲模式已更新 potion.moveSpeed = 移動加速 potion.moveSlowdown = 移動減速 potion.digSpeed = 挖掘加速 potion.digSlowDown = 挖掘減速 potion.damageBoost = 增加攻擊力 potion.heal = 立即治療 potion.harm = 立即傷害 potion.jump = 跳躍提升 potion.confusion = 噁心 potion.regeneration = 恢復 potion.resistance = 抗性 potion.fireResistance = 抗火性 potion.waterBreathing = 水下呼吸 potion.invisibility = 隱身 potion.blindness = 失明 potion.nightVision = 夜視 potion.hunger = 飢餓 potion.weakness = 虛弱 potion.poison = 中毒 potion.wither = 凋零 potion.healthBoost = 生命值提升 potion.absorption = 吸收 potion.saturation = 飽食度 commands.generic.exception = 嘗試執行此指令時發生未知錯誤 commands.generic.permission = 您沒有權限使用此指令 commands.generic.notFound = 未知的指令。請使用 /help 來顯示指令列表。 commands.generic.player.notFound = 找不到該玩家 commands.generic.usage = 用法:{%0} commands.generic.level = 地圖名 commands.generic.seed = 種子碼 commands.generic.name = 名稱 commands.generic.generator = 生成器名稱 commands.generic.opt.missing = 指令缺少參數,請確認後重新輸入。 commands.generic.runingame = 請在遊戲中使用該指令 commands.time.added = 時間增加了 {%0} commands.time.set = 時間設定為 {%0} commands.time.query = 現在時間是 {%0} commands.me.usage = /me commands.give.item.notFound = ID為 {%0} 的物品並不存在 commands.give.success = 將 {%0} * {%1} 給 {%2} commands.give.tagError = 數據格式不正確: {%0} commands.effect.usage = /effect <玩家名稱> <效果> [秒數] [倍數] [隱藏粒子] 或 /effect <玩家名稱> clear commands.effect.notFound = ID為 {%0} 的特殊效果並不存在 commands.effect.success = 對 {%3} 加上了 {%4} 秒的 {%0} (ID {%1}) * {%2} commands.effect.success.removed = 從 {%1} 身上移除了 {%0} commands.effect.success.removed.all = 已解除 {%0} 身上所有特殊狀態 commands.effect.failure.notActive = 無法從 {%1} 身上移除 {%0},因為其身上無此效果 commands.effect.failure.notActive.all = 無法移除效果因為 {%0} 身上沒有任何效果 commands.enchant.maxLevel = 這個附魔的等級範圍是 1 - {%0} commands.enchant.noItem = 目標沒有手持一樣物品 commands.enchant.notFound = 沒有一個附魔ID為 {%0} commands.enchant.success = 附魔完成 commands.enchant.cantEnchant = 這件物品不能被附魔! commands.enchant.usage = /enchant <玩家名稱> <附魔ID> [物品等級] commands.particle.success = 正在應用 {%0} 效果 {%1} 次 commands.particle.notFound = 未知的效果名稱 {%0} commands.players.usage = /list commands.players.list = 共有 {%0}/{%1} 玩家在線上: commands.kill.successful = 已刪除 {%0} commands.banlist.ips = 共有 %d 個被封鎖的 IP 位址: commands.banlist.players = 共有 {%0} 個被封鎖的玩家: commands.banlist.cids = 共有 {%0} 禁止的 CIDs: commands.banlist.usage = /banlist [ips|players] commands.defaultgamemode.usage = /defaultgamemode <模式> commands.defaultgamemode.success = 預設遊戲模式已設定為 {%0} commands.op.success = {%0} 獲得管理員權限 commands.op.usage = /op <玩家名稱> commands.deop.success = {%0} 移除管理員 commands.deop.usage = /deop <玩家名稱> commands.say.usage = /say <訊息...> commands.seed.usage = /seed commands.seed.success = 種子碼:{%0} commands.bancidbyname.success = 封鎖玩家 {%0} 的CID commands.bancidbyname.usage = /bancidbyname <玩家名稱> commands.bancid.success = 封鎖CID: {%0} commands.bancid.usage = /bancid <設備ID> commands.unbancid.usage = /pardoncid <設備ID> commands.unbancid.success = Unbanned CID {%0} commands.ban.success = 封鎖玩家 {%0} commands.ban.usage = /ban <玩家名稱> [原因...] [時間(天)] commands.unban.success = 解封玩家 {%0} commands.unban.usage = /pardon <玩家名稱> commands.banip.invalid = 您輸入了一個無效的 IP 位址、玩家名稱或不在線上 commands.banip.success = 封鎖 IP 位址 {%0} 。 commands.banip.success.players = 封鎖 IP 位址 {%0} 來自 {%1} commands.banip.usage = /ban-ip [原因 ...] commands.unbanip.invalid = 您輸入了一個無效的 IP 位址 commands.unbanip.success = 解除封鎖 IP 位址 {%0} commands.unbanip.usage = /pardon-ip commands.banipbyname.success = 封鎖玩家 {%0} 的IP commands.banipbyname.usage = /banipbyname <玩家名稱> commands.save.usage = /save-all commands.save-on.usage = /save-on commands.save-off.usage = /save-off commands.save.enabled = 開啟地圖自動存檔功能 commands.save.disabled = 關閉地圖自動存檔功能 commands.save.start = 正在儲存... commands.save.success = 儲存完畢 commands.setblock.usage = /setblock <方塊名> [數據值] command.setblock.invalidBlock = 無效的方塊名稱/ID commands.stop.usage = /stop commands.stop.start = 停止伺服器中 commands.kick.success = {%0} 從遊戲中被踢出 commands.kick.success.reason = {%0} 從遊戲中被踢出:{%1} commands.kick.usage = /kick <玩家名稱> [原因...] commands.tp.success = 已傳送 {%0} 至 {%1} commands.tp.success.coordinates = 已傳送 {%0} 至 {%1}, {%2}, {%3} commands.tp.usage = /tp [玩家名稱] <目標玩家> 或是 /tp [玩家名稱] [ ] commands.whitelist.list = 有 {%0} 人(全部 {%1} 人) 為白名單玩家: commands.whitelist.enabled = 已開啟白名單 commands.whitelist.disabled = 已關閉白名單 commands.whitelist.reloaded = 重置白名單 commands.whitelist.add.success = 新增 {%0} 至白名單 commands.whitelist.add.usage = /whitelist add <玩家名稱> commands.whitelist.remove.success = 從白名單刪除 {%0} commands.whitelist.remove.usage = /whitelist remove <玩家名稱> commands.whitelist.usage = /whitelist commands.gamemode.success.self = 設定自身的遊戲模式為 {%2} commands.gamemode.success.other = 設定 {%0} 的遊戲模式為 {%1} commands.gamemode.usage = /gamemode <模式> [玩家名稱] commands.help.header = --- 查看幫助列表第 {%0} 頁共 {%1} 頁 (/help ) --- commands.help.usage = /help [頁數|指令名稱] commands.message.usage = /tell <玩家名稱> <訊息...> commands.message.sameTarget = 您不能傳送訊息給自己! commands.xp.usage = /xp <經驗值或等級+L> <玩家名稱> commands.difficulty.usage = /difficulty <難度> commands.difficulty.success = 設定遊戲難度為 {%0} commands.spawnpoint.usage = /spawnpoint [玩家名稱] [ ] commands.spawnpoint.success = 設定 {%0} 的重生點為 ({%1}, {%2}, {%3}) commands.setworldspawn.usage = /setworldspawn [ ] commands.setworldspawn.success = 設定世界重生點為 ({%0}, {%1}, {%2}) commands.summon.usage = /summon <實體名稱> [ ] [數據標籤] # -------------------- PocketMine language files, only for console -------------------- pocketmine.data.playerNotFound = 無法找到玩家數據 "{%0}",正在創建新的數據檔 pocketmine.data.playerCorrupted = 發現損壞的數據 "{%0}",創建新的設定檔 pocketmine.data.playerOld = 發現舊的玩家數據 "{%0}",更新設定檔 pocketmine.data.saveError = 無法儲存 "{%0}" 的玩家資料:{%1} pocketmine.level.notFound = 無法找到 "{%0}" 地圖 pocketmine.level.loadError = 無法讀取地圖 "{%0}":{%1} pocketmine.level.generationError = 無法產生地圖 "{%0}":{%1} pocketmine.level.tickError = 計算地圖「{%0}」時出現錯誤︰{%1} pocketmine.level.chunkUnloadError = 移除一個區塊時發生錯誤:{%0} pocketmine.level.backgroundGeneration = 正在於背景生成世界 「{%0}」 的地形 pocketmine.level.defaultError = 沒有讀取預設的地圖 pocketmine.level.preparing = 準備地圖中... "{%0}" pocketmine.level.unloading = 正在移除地圖 "{%0}" pocketmine.server.start = 正在啟動支援 Minecraft:PE {%0} 版本的伺服器 pocketmine.server.networkError = [網路] 停止接口 {%0} 由於 {%1} pocketmine.server.networkStart = 正在啟動伺服器在 {%0}:{%1} pocketmine.server.info = 此伺服器正在運作 {%0} {%1} 版本 "{%2}" (API {%3}) pocketmine.server.info.extended = 此伺服器正在運作 {%0} {%1} 「{%2}」 執行 API 版本 {%3} 支援 Minecraft:PE {%4} (協定版本 {%5}) pocketmine.server.info.extended1 = 本伺服器正運行 {%0}{%1} ({%2}) (代號 "{%2}") pocketmine.server.info.extended2 = PHP 版本: {%0} pocketmine.server.info.extended3 = API: {%0} pocketmine.server.info.extended4 = 目標用戶端: Minecraft PE {%0} pocketmine.server.info.extended5 = 協議版本: {%0} pocketmine.server.license = {%0} 根據 LGPL 許可發佈 pocketmine.server.tickOverload = 注意!伺服器有超載的可能 pocketmine.server.startFinished = 讀取完成 ({%0}s)!如需幫助,請輸入 "help" 或 "?" pocketmine.server.defaultGameMode = 預設的遊戲類型:{%0} pocketmine.server.query.start = 啟動 GS4 狀態監聽器 pocketmine.server.query.info = 設定 query 接口到 {%0} pocketmine.server.query.running = Query 運作在 {%0}:{%1} pocketmine.command.alias.illegal = 不能註冊別名 {%0},因為它包含非法字符 pocketmine.command.alias.notFound = 未能登記別稱 {%0} ,因為它包含不存在的指令: {%1} pocketmine.command.exception = 於 {%1} 執行指令 「{%0}「 時,出現了未被處理的錯誤: {%2} pocketmine.commands.cave.usage = /cave <旋轉角度> <洞穴長度> <分叉數> <洞穴強度> | /cave getmypos pocketmine.commands.cave.info = 旋轉角度:{%0} 洞穴長度:{%1} 分叉數:{%2} 洞穴強度:{%3} pocketmine.commands.cave.start = 開始生成礦洞,可能需要較多時間 pocketmine.commands.cave.success = 礦洞生成完畢啦 pocketmine.command.plugins.description = 獲取在伺服器上運行的插件列表 pocketmine.command.plugins.success = 插件 ({%0}):{%1} pocketmine.command.plugins.usage = /plugins pocketmine.command.reload.description = 重新讀取伺服器設定和插件 pocketmine.command.reload.usage = /reload pocketmine.command.reload.reloading = 重新讀取伺服器... pocketmine.command.reload.reloaded = 重新讀取完成 pocketmine.command.lvdat.description = 修改地圖屬性 pocketmine.command.lvdat.changed = 對地圖{%0}的{%1}屬性修改已保存,部分屬性可能需要重啟伺服器後生效。 pocketmine.command.lvdat.fixname = 已修正地圖{%0}的名稱,建議重啟伺服器。 pocketmine.command.lvdat.nofound = 地圖{%0}沒有創建或者加載失敗。 pocketmine.command.lvdat.preset = 生成器選項(預設) pocketmine.command.status.description = 重新讀取伺服器的性能。 pocketmine.command.status.usage = /status pocketmine.command.status.title = 伺服器狀態 pocketmine.command.status.player = 伺服器人數: pocketmine.command.status.days = 天 pocketmine.command.status.hours = 小時 pocketmine.command.status.minutes = 分 pocketmine.command.status.seconds = 秒 pocketmine.command.status.uptime = 運行時間: pocketmine.command.status.AverageTPS = 平均TPS: pocketmine.command.status.CurrentTPS = 瞬時TPS: pocketmine.command.status.Networkupload = 網路上傳: pocketmine.command.status.Networkdownload = 網路下載: pocketmine.command.status.Threadcount = 線程總數: pocketmine.command.status.Mainmemory = 線程總數: pocketmine.command.status.Totalmemory = 總記憶體: pocketmine.command.status.Totalvirtualmemory = 總虛擬記憶體: pocketmine.command.status.Heapmemory = 堆棧記憶體: pocketmine.command.status.Maxmemorysystem = 系統最大記憶體: pocketmine.command.status.Maxmemorymanager = 核心全域最大記憶體: pocketmine.command.status.World = 世界 pocketmine.command.status.chunks = 區塊, pocketmine.command.status.entities = 實體, pocketmine.command.status.tiles = tiles. pocketmine.command.status.Time = 時間 pocketmine.command.status.ms = 毫秒 pocketmine.command.gc.description = 啟動垃圾清除任務 pocketmine.command.gc.usage = /gc pocketmine.command.gc.title = 垃圾回收結果 pocketmine.command.gc.chunks = 區塊: pocketmine.command.gc.entities = 實體: pocketmine.command.gc.tiles = 方塊: pocketmine.command.gc.cycles = 循環: pocketmine.command.gc.memory = 記憶體釋放: pocketmine.command.biome.description = 設定指定地圖生物群系 pocketmine.command.biome.posset = 已設定第{%3}個坐標為:({%1},{%2})[{%0}] pocketmine.command.biome.get = 您所在的生物群系 ID 為:{%0} 顏色值為:{%1},{%2},{%3} pocketmine.command.biome.wrongLev = 不能跨地圖設定取點。 pocketmine.command.biome.wrongBio = 錯誤的生物群系ID,請輸入數字ID(1=草原,2=沙漠,13=雪山,6=沼澤) pocketmine.command.biome.wrongCol = 錯誤的生物群系顏色,例如 146,188,89 可以使用 /biome get 獲取 pocketmine.command.biome.noPos = 請先通過 /biome pos1|pos2 設定範圍 pocketmine.command.biome.set = 已成功設定生物群系為:{%0} pocketmine.command.biome.color = 已成功設定生態顏色為:{%0},{%1},{%2} pocketmine.command.timings.description = 紀錄計時數據,以檢視伺服器的性能。 pocketmine.command.timings.usage = /timings pocketmine.command.timings.enable = 啟用定時和重啟 pocketmine.command.timings.disable = 停用定時 pocketmine.command.timings.timingsDisabled = 啟用定時工具透過 /timings on pocketmine.command.timings.reset = 定時重啟 pocketmine.command.timings.pasteError = 已記錄在事件記錄檔中 pocketmine.command.timings.timingsUpload = 計時數據已被上載至 {%0} pocketmine.command.timings.timingsRead = 你可以在 {%0} 閱讀計時結果 pocketmine.command.timings.timingsWrite = 計時數據已被儲存至 {%0} pocketmine.command.version.description = 檢視此伺服器 (及其使用的插件) 的版本 pocketmine.command.version.usage = /version [插件名稱] pocketmine.command.version.noSuchPlugin = 該伺服器沒有運行任何叫這個名稱的插件。使用 /plugins 來獲得插件列表。 pocketmine.command.give.description = 給指定玩家一定數量的物品 pocketmine.command.give.usage = /give <玩家名稱> <物品[:耐久度]> [數量] [附加數據值] pocketmine.command.kill.description = 自殺或殺死其他玩家 pocketmine.command.kill.usage = /kill [玩家名稱] pocketmine.command.particle.description = 加入粒子效果至世界 pocketmine.command.particle.usage = /particle <玩家名稱> [數量] [數據值] pocketmine.command.time.description = 更改每個世界的時間 pocketmine.command.time.usage = /time <數值> 或 /time pocketmine.command.bancidbyname.description = 禁止指定玩家的設備 ID pocketmine.command.bancid.description = 禁止指定的設備 ID pocketmine.command.banipbyname.description = 禁止指定玩家的 IP pocketmine.command.ban.player.description = 禁止指定的玩家使用此伺服器 pocketmine.command.ban.ip.description = 禁止指定的 IP 位址使用此伺服器 pocketmine.command.banlist.description = 查看來自該伺服器禁止的所有玩家 pocketmine.command.defaultgamemode.description = 設定預設的遊戲模式 pocketmine.command.deop.description = 移除指定玩家的管理員權限 pocketmine.command.difficulty.description = 設定遊戲的難易度 pocketmine.command.enchant.description = 把物件附魔 pocketmine.command.effect.description = 增加/減少玩家身上的效果 pocketmine.command.gamemode.description = 改變玩家到一個特定的遊戲模式 pocketmine.command.help.description = 顯示幫助列表 pocketmine.command.kick.description = 從伺服器中刪除指定玩家 pocketmine.command.list.description = 顯示在線玩家列表 pocketmine.command.me.description = 於聊天中作出指定的動作 pocketmine.command.op.description = 賦予指定玩家管理員權限 pocketmine.command.unban.cid.description = 允許指定 CID 使用此伺服器 pocketmine.command.unban.player.description = 允許指定玩家使用此伺服器 pocketmine.command.unban.ip.description = 允許指定 IP 位址使用此伺服器 pocketmine.command.save.description = 儲存伺服器到磁碟上 pocketmine.command.saveoff.description = 停用自動儲存伺服器 pocketmine.command.saveon.description = 啟用自動儲存伺服器 pocketmine.command.say.description = 以發送指令者身份廣播指定的訊息 pocketmine.command.seed.description = 顯示世界種子碼 pocketmine.command.setworldspawn.description = 設定一個世界重生點。未指定坐標,將使用玩家的坐標。 pocketmine.command.spawnpoint.description = 設定玩家重生點 pocketmine.command.stop.description = 關閉伺服器 pocketmine.command.tp.description = 傳送指定玩家(或是自己)到另一位玩家或座標 pocketmine.command.tell.description = 傳送私訊給指定玩家 pocketmine.command.xp.description = 給指定玩家新增經驗值或等級 pocketmine.command.summon.description = 召喚指定的實體於玩家位置或指定位置 ocketmine.command.fill.description = 填充了指定方塊 pocketmine.command.setblock.description = 將一個方塊更改為另一個方塊 pocketmine.command.weather.description = 設定指定地圖的天氣 pocketmine.command.weather.usage = /weather <地圖名稱 天氣|天氣> pocketmine.command.weather.changed = 成功設定地圖{%0}的天氣 pocketmine.command.weather.noregistered = 地圖{%0}未註冊到天氣管理器 pocketmine.command.weather.invalid = 無效的天氣!請輸入天氣 0,1,2,3。 pocketmine.command.weather.wrong = 錯誤的參數 pocketmine.command.weather.invalid.level = 錯誤的地圖名稱 pocketmine.command.whitelist.description = 管理員允許使用此伺服器的玩家列表 pocketmine.crash.create = 一個不能回復的錯誤發生了,使伺服器崩潰。正在儲存錯誤報告。 pocketmine.crash.error = 未能儲存錯誤報告︰{%0} pocketmine.crash.submit = 請上載檔案「{%0}」至線上崩潰儲存庫,並把所獲之連結提交至漏洞報告網頁。請盡量提供更多資料。 pocketmine.crash.archive = 毀損傾印報告已經自動地被提交到毀損傾印存檔。你可以在{%0} 查看到它或使用ID #{%1}。 pocketmine.debug.enable = 啟用 LevelDB 支援 pocketmine.player.invalidMove = {%0} 行動可疑! pocketmine.player.logIn = {%0}[/{%1}:{%2}] [ClientID: {%3}] 登入遊戲,實體ID為 {%4} 座標位在 ({%5}, {%6}, {%7}, {%8}) pocketmine.player.logOut = {%0}[/{%1}:{%2}] 登出遊戲,原因: {%3} pocketmine.player.transferred = {%0}[/{%1}:{%2}] 被傳送到 {%3} pocketmine.player.invalidEntity = {%0} 嘗試攻擊一個無效的實體 pocketmine.plugin.load = 讀取中... {%0} pocketmine.plugin.enable = 開啟中... {%0} pocketmine.plugin.disable = 關閉中... {%0} pocketmine.plugin.restrictedName = 受限的名稱 pocketmine.plugin.incompatibleAPI = 不相容的API版本 pocketmine.plugin.unknownDependency = 本插件無法單獨使用 pocketmine.plugin.circularDependency = 檢測出循環依賴 pocketmine.plugin.genericLoadError = 無法讀取插件 '{%0}' pocketmine.plugin.spacesDiscouraged = 插件 '{%0}' 在名稱中使用了空格,不建議這樣做 pocketmine.plugin.loadError = 無法讀取插件 '{%0}':{%1} pocketmine.plugin.duplicateError = 無法讀取插件 '{%0}':已有相同插件 pocketmine.plugin.fileError = 無法讀取在 '{%1}' 資料夾中的 '{%0}':{%2} pocketmine.plugin.commandError = 無法讀取 {%1} 插件的 {%0} 指令 pocketmine.plugin.aliasError = 無法讀取 {%1} 插件的 {%0} 別名 pocketmine.plugin.deprecatedEvent = 插件 '{%0}' 已經使用 '{%2}' 方法註冊了一個在 '{%1}' 的監聽器,但是該事件已過時。 pocketmine.plugin.eventError = "無法處理事件 '{%0}' 至 '{%1}':{%2} 在 {%3} 上" pocketmine.resourcepacks.createFolder = 資源包路徑 {%0} 不存在,正在創建新的資料夾 pocketmine.resourcepacks.notFolder = 資源包路徑 {%0} 存在,但不是一個資料夾 pocketmine.resourcepacks.load = 讀取資源包... pocketmine.resourcepacks.folderNotSupported = 暫不支持資料夾資源包 {%0} ,請壓縮 pocketmine.resourcepacks.unsupportedType = 暫不支持類型未知的資源包 {%0} pocketmine.resourcepacks.packNotFound = 無法找到資源包 {%0} pocketmine.resourcepacks.loadFinished = 已讀取 {%0} 個資源包 level->getBlockLightAt($x, $y, $z); } public function setLight(int $x, int $y, int $z, int $level){ $this->level->setBlockLightAt($x, $y, $z, $level); } }registerChunkLoader($this, $chunkX, $chunkZ) * Unregister Level->unregisterChunkLoader($this, $chunkX, $chunkZ) * * WARNING: When moving this object around in the world or destroying it, * be sure to free the existing references from Level, otherwise you'll leak memory. */ interface ChunkLoader { /** * Returns the ChunkLoader id. * Call Level::generateChunkLoaderId($this) to generate and save it * * @return int */ public function getLoaderId(); /** * Returns if the chunk loader is currently active * * @return bool */ public function isLoaderActive(); /** * @return Position */ public function getPosition(); /** * @return float */ public function getX(); /** * @return float */ public function getZ(); /** * @return Level */ public function getLevel(); /** * This method will be called when a Chunk is replaced by a new one * * @param Chunk $chunk */ public function onChunkChanged(Chunk $chunk); /** * This method will be called when a registered chunk is loaded * * @param Chunk $chunk */ public function onChunkLoaded(Chunk $chunk); /** * This method will be called when a registered chunk is unloaded * * @param Chunk $chunk */ public function onChunkUnloaded(Chunk $chunk); /** * This method will be called when a registered chunk is populated * Usually it'll be sent with another call to onChunkChanged() * * @param Chunk $chunk */ public function onChunkPopulated(Chunk $chunk); /** * This method will be called when a block changes in a registered chunk * * @param Block|Vector3 $block */ public function onBlockChanged(Vector3 $block); }level = $center->getLevel(); $this->source = $center; $this->size = max($size, 0); $this->what = $what; $this->dropItem = $dropItem; } /** * @return bool */ public function explodeA() : bool{ if($this->size < 0.1){ return false; } $vector = new Vector3(0, 0, 0); $vBlock = new Vector3(0, 0, 0); $mRays = intval($this->rays - 1); for($i = 0; $i < $this->rays; ++$i){ for($j = 0; $j < $this->rays; ++$j){ for($k = 0; $k < $this->rays; ++$k){ if($i === 0 or $i === $mRays or $j === 0 or $j === $mRays or $k === 0 or $k === $mRays){ $vector->setComponents($i / $mRays * 2 - 1, $j / $mRays * 2 - 1, $k / $mRays * 2 - 1); $vector->setComponents(($vector->x / ($len = $vector->length())) * $this->stepLen, ($vector->y / $len) * $this->stepLen, ($vector->z / $len) * $this->stepLen); $pointerX = $this->source->x; $pointerY = $this->source->y; $pointerZ = $this->source->z; for($blastForce = $this->size * (mt_rand(700, 1300) / 1000); $blastForce > 0; $blastForce -= $this->stepLen * 0.75){ $x = (int) $pointerX; $y = (int) $pointerY; $z = (int) $pointerZ; $vBlock->x = $pointerX >= $x ? $x : $x - 1; $vBlock->y = $pointerY >= $y ? $y : $y - 1; $vBlock->z = $pointerZ >= $z ? $z : $z - 1; if($vBlock->y < 0 or $vBlock->y >= Level::Y_MAX){ break; } $block = $this->level->getBlock($vBlock); if($block->getId() !== 0){ $blastForce -= ($block->getResistance() / 5 + 0.3) * $this->stepLen; if($blastForce > 0){ if(!isset($this->affectedBlocks[$index = Level::blockHash($block->x, $block->y, $block->z)])){ $this->affectedBlocks[$index] = $block; } } } $pointerX += $vector->x; $pointerY += $vector->y; $pointerZ += $vector->z; } } } } } return true; } /** * @return bool */ public function explodeB() : bool{ $send = []; $updateBlocks = []; $source = (new Vector3($this->source->x, $this->source->y, $this->source->z))->floor(); $yield = (1 / $this->size) * 100; if($this->what instanceof Entity){ $this->level->getServer()->getPluginManager()->callEvent($ev = new EntityExplodeEvent($this->what, $this->source, $this->affectedBlocks, $yield)); if($ev->isCancelled()){ return false; }else{ $yield = $ev->getYield(); $this->affectedBlocks = $ev->getBlockList(); } } $explosionSize = $this->size * 2; $minX = Math::floorFloat($this->source->x - $explosionSize - 1); $maxX = Math::ceilFloat($this->source->x + $explosionSize + 1); $minY = Math::floorFloat($this->source->y - $explosionSize - 1); $maxY = Math::ceilFloat($this->source->y + $explosionSize + 1); $minZ = Math::floorFloat($this->source->z - $explosionSize - 1); $maxZ = Math::ceilFloat($this->source->z + $explosionSize + 1); $explosionBB = new AxisAlignedBB($minX, $minY, $minZ, $maxX, $maxY, $maxZ); $list = $this->level->getNearbyEntities($explosionBB, $this->what instanceof Entity ? $this->what : null); foreach($list as $entity){ $distance = $entity->distance($this->source) / $explosionSize; if($distance <= 1){ $motion = $entity->subtract($this->source)->normalize(); $impact = (1 - $distance) * ($exposure = 1); $damage = (int) ((($impact * $impact + $impact) / 2) * 8 * $explosionSize + 1); if($this->what instanceof Entity){ $ev = new EntityDamageByEntityEvent($this->what, $entity, EntityDamageEvent::CAUSE_ENTITY_EXPLOSION, $damage); }elseif($this->what instanceof Block){ $ev = new EntityDamageByBlockEvent($this->what, $entity, EntityDamageEvent::CAUSE_BLOCK_EXPLOSION, $damage); }else{ $ev = new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_BLOCK_EXPLOSION, $damage); } if($entity->attack($ev->getFinalDamage(), $ev) === true){ $ev->useArmors(); } $entity->setMotion($motion->multiply($impact)); } } $air = Item::get(Item::AIR); foreach($this->affectedBlocks as $block){ if($block->getId() === Block::TNT){ $mot = (new Random())->nextSignedFloat() * M_PI * 2; $tnt = Entity::createEntity("PrimedTNT", $this->level, new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $block->x + 0.5), new DoubleTag("", $block->y), new DoubleTag("", $block->z + 0.5) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", -sin($mot) * 0.02), new DoubleTag("", 0.2), new DoubleTag("", -cos($mot) * 0.02) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), "Fuse" => new ByteTag("Fuse", mt_rand(10, 30)) ])); $tnt->spawnToAll(); }elseif($this->dropItem and mt_rand(0, 100) < $yield){ foreach($block->getDrops($air) as $drop){ $this->level->dropItem($block->add(0.5, 0.5, 0.5), Item::get(...$drop)); } } $this->level->setBlockIdAt($block->x, $block->y, $block->z, 0); $pos = new Vector3($block->x, $block->y, $block->z); for($side = 0; $side < 5; $side++){ $sideBlock = $pos->getSide($side); if(!isset($this->affectedBlocks[$index = Level::blockHash($sideBlock->x, $sideBlock->y, $sideBlock->z)]) and !isset($updateBlocks[$index])){ $this->level->getServer()->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->level->getBlock($sideBlock))); if(!$ev->isCancelled()){ $ev->getBlock()->onUpdate(Level::BLOCK_UPDATE_NORMAL); } $updateBlocks[$index] = true; } } $send[] = new Vector3($block->x - $source->x, $block->y - $source->y, $block->z - $source->z); } $pk = new ExplodePacket(); $pk->x = $this->source->x; $pk->y = $this->source->y; $pk->z = $this->source->z; $pk->radius = $this->size; $pk->records = $send; $this->level->addChunkPacket($source->x >> 4, $source->z >> 4, $pk); $this->level->addParticle(new HugeExplodeSeedParticle($source)); $this->level->broadcastLevelSoundEvent($source, LevelSoundEventPacket::SOUND_EXPLODE); return true; } } class Level implements ChunkManager, Metadatable { private static $levelIdCounter = 1; private static $chunkLoaderCounter = 1; public static $COMPRESSION_LEVEL = 8; const Y_MASK = 0xFF; const Y_MAX = 0x100; //256 const BLOCK_UPDATE_NORMAL = 1; const BLOCK_UPDATE_RANDOM = 2; const BLOCK_UPDATE_SCHEDULED = 3; const BLOCK_UPDATE_WEAK = 4; const BLOCK_UPDATE_TOUCH = 5; const TIME_DAY = 0; const TIME_SUNSET = 12000; const TIME_NIGHT = 14000; const TIME_SUNRISE = 23000; const TIME_FULL = 24000; const DIMENSION_NORMAL = 0; const DIMENSION_NETHER = 1; const DIMENSION_END = 2; /** @var Tile[] */ private $tiles = []; private $motionToSend = []; private $moveToSend = []; /** @var Player[] */ private $players = []; /** @var Entity[] */ private $entities = []; /** @var Entity[] */ public $updateEntities = []; /** @var Tile[] */ public $updateTiles = []; private $blockCache = []; /** @var DataPacket[] */ private $chunkCache = []; private $cacheChunks = false; private $sendTimeTicker = 0; /** @var Server */ private $server; /** @var int */ private $levelId; /** @var LevelProvider */ private $provider; /** @var ChunkLoader[] */ private $loaders = []; /** @var int[] */ private $loaderCounter = []; /** @var ChunkLoader[][] */ private $chunkLoaders = []; /** @var Player[][] */ private $playerLoaders = []; /** @var DataPacket[] */ private $chunkPackets = []; /** @var float[] */ private $unloadQueue; private $time; public $stopTime; private $folderName; /** @var Chunk[] */ private $chunks = []; /** @var Vector3[][] */ private $changedBlocks = []; /** @var ReversePriorityQueue */ private $updateQueue; private $updateQueueIndex = []; /** @var Player[][] */ private $chunkSendQueue = []; private $chunkSendTasks = []; private $chunkPopulationQueue = []; private $chunkPopulationLock = []; private $chunkGenerationQueue = []; private $chunkGenerationQueueSize = 8; private $chunkPopulationQueueSize = 2; private $autoSave = true; /** @var BlockMetadataStore */ private $blockMetadata; /** @var Position */ private $temporalPosition; /** @var Vector3 */ private $temporalVector; /** @var \SplFixedArray */ private $blockStates; public $sleepTicks = 0; private $chunkTickRadius; private $chunkTickList = []; private $chunksPerTick; private $clearChunksOnTick; private $randomTickBlocks = [ Block::GRASS => Grass::class, Block::SAPLING => Sapling::class, Block::LEAVES => Leaves::class, Block::WHEAT_BLOCK => Wheat::class, Block::COCOA_BLOCK => CocoaBlock::class, Block::FARMLAND => Farmland::class, Block::SNOW_LAYER => SnowLayer::class, Block::ICE => Ice::class, Block::CACTUS => Cactus::class, Block::SUGARCANE_BLOCK => Sugarcane::class, Block::RED_MUSHROOM => RedMushroom::class, Block::BROWN_MUSHROOM => BrownMushroom::class, Block::PUMPKIN_STEM => PumpkinStem::class, Block::NETHER_WART_BLOCK => NetherWart::class, Block::MELON_STEM => MelonStem::class, //Block::VINE => true, Block::MYCELIUM => Mycelium::class, //Block::COCOA_BLOCK => true, Block::CARROT_BLOCK => Carrot::class, Block::POTATO_BLOCK => Potato::class, Block::LEAVES2 => Leaves2::class, Block::BEETROOT_BLOCK => Beetroot::class, ]; /** @var LevelTimings */ public $timings; private $tickRate; public $tickRateTime = 0; public $tickRateCounter = 0; /** @var Generator */ private $generator; /** @var Generator */ private $generatorInstance; private $closed = false; /** @var Weather */ private $weather; private $blockTempData = []; private $dimension = self::DIMENSION_NORMAL; /** * This method is internal use only. Do not use this in plugins * * @param Vector3 $pos * @param $data */ public function setBlockTempData(Vector3 $pos, $data = null) { if ($data == null and isset($this->blockTempData[self::blockHash($pos->x, $pos->y, $pos->z)])) { unset($this->blockTempData[self::blockHash($pos->x, $pos->y, $pos->z)]); } else { $this->blockTempData[self::blockHash($pos->x, $pos->y, $pos->z)] = $data; } } /** * This method is internal use only. Do not use this in plugins * * @param Vector3 $pos * @return int */ public function getBlockTempData(Vector3 $pos) { if (isset($this->blockTempData[self::blockHash($pos->x, $pos->y, $pos->z)])) { return $this->blockTempData[self::blockHash($pos->x, $pos->y, $pos->z)]; } return 0; } /** * Returns the chunk unique hash/key * * @param int $x * @param int $z * * @return string */ public static function chunkHash(int $x, int $z) { return PHP_INT_SIZE === 8 ? (($x & 0xFFFFFFFF) << 32) | ($z & 0xFFFFFFFF) : $x . ":" . $z; } public static function blockHash(int $x, int $y, int $z) { return PHP_INT_SIZE === 8 ? (($x & 0xFFFFFFF) << 36) | (($y & Level::Y_MASK) << 28) | ($z & 0xFFFFFFF) : $x . ":" . $y . ":" . $z; } public static function getBlockXYZ($hash, &$x, &$y, &$z) { if (PHP_INT_SIZE === 8) { $x = $hash >> 36; $y = ($hash >> 28) & Level::Y_MASK; //it's always positive $z = ($hash & 0xFFFFFFF) << 36 >> 36; } else { $hash = explode(":", $hash); $x = (int)$hash[0]; $y = (int)$hash[1]; $z = (int)$hash[2]; } } public static function getXZ($hash, &$x, &$z) { if (PHP_INT_SIZE === 8) { $x = $hash >> 32; $z = ($hash & 0xFFFFFFFF) << 32 >> 32; } else { $hash = explode(":", $hash); $x = (int)$hash[0]; $z = (int)$hash[1]; } } public static function generateChunkLoaderId(ChunkLoader $loader): int { if ($loader->getLoaderId() === 0 or $loader->getLoaderId() === null or $loader->getLoaderId() === null) { return self::$chunkLoaderCounter++; } else { throw new \InvalidStateException("ChunkLoader has a loader id already assigned: " . $loader->getLoaderId()); } } /** * Init the default level data * * @param Server $server * @param string $name * @param string $path * @param string $provider Class that extends LevelProvider * * @throws \Throwable */ public function __construct(Server $server, string $name, string $path, string $provider) { $this->blockStates = Block::$fullList; $this->levelId = static::$levelIdCounter++; $this->blockMetadata = new BlockMetadataStore($this); $this->server = $server; $this->autoSave = $server->getAutoSave(); /** @var LevelProvider $provider */ if (is_subclass_of($provider, LevelProvider::class, true)) { $this->provider = new $provider($this, $path); } else { throw new LevelException("Provider is not a subclass of LevelProvider"); } $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.level.preparing", [$this->provider->getName()])); $this->generator = Generator::getGenerator($this->provider->getGenerator()); $this->folderName = $name; $this->updateQueue = new ReversePriorityQueue(); $this->updateQueue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); $this->time = (int)$this->provider->getTime(); $this->chunkTickRadius = min($this->server->getViewDistance(), max(1, (int)$this->server->getProperty("chunk-ticking.tick-radius", 4))); $this->chunksPerTick = (int)$this->server->getProperty("chunk-ticking.per-tick", 40); $this->chunkGenerationQueueSize = (int)$this->server->getProperty("chunk-generation.queue-size", 8); $this->chunkPopulationQueueSize = (int)$this->server->getProperty("chunk-generation.population-queue-size", 2); $this->chunkTickList = []; $this->clearChunksOnTick = (bool)$this->server->getProperty("chunk-ticking.clear-tick-list", true); $this->cacheChunks = (bool)$this->server->getProperty("chunk-sending.cache-chunks", false); $this->timings = new LevelTimings($this); $this->temporalPosition = new Position(0, 0, 0, $this); $this->temporalVector = new Vector3(0, 0, 0); $this->tickRate = 1; $this->weather = new Weather($this, 0); $this->setDimension(self::DIMENSION_NORMAL); if ($this->server->netherEnabled and $this->server->netherName == $this->folderName) $this->setDimension(self::DIMENSION_NETHER); elseif ($this->server->enderEnabled and $this->server->enderName == $this->folderName) $this->setDimension(self::DIMENSION_END); if ($this->server->weatherEnabled and $this->getDimension() == self::DIMENSION_NORMAL) { $this->weather->setCanCalculate(true); } else $this->weather->setCanCalculate(false); } public function setDimension(int $dimension) { $this->dimension = $dimension; } public function getDimension(): int { return $this->dimension; } /** * @return Weather */ public function getWeather() { return $this->weather; } public function getTickRate(): int { return $this->tickRate; } public function getTickRateTime() { return $this->tickRateTime; } public function setTickRate(int $tickRate) { $this->tickRate = $tickRate; } public function initLevel() { $generator = $this->generator; $this->generatorInstance = new $generator($this->provider->getGeneratorOptions()); $this->generatorInstance->init($this, new Random($this->getSeed())); $this->registerGenerator(); } public function getWaterHeight(): int { if ($this->generatorInstance instanceof Generator) { return $this->generatorInstance->getWaterHeight(); } return 0; } public function registerGenerator() { $size = $this->server->getScheduler()->getAsyncTaskPoolSize(); for ($i = 0; $i < $size; ++$i) { $this->server->getScheduler()->scheduleAsyncTaskToWorker(new GeneratorRegisterTask($this, $this->generatorInstance), $i); } } public function unregisterGenerator() { $size = $this->server->getScheduler()->getAsyncTaskPoolSize(); for ($i = 0; $i < $size; ++$i) { $this->server->getScheduler()->scheduleAsyncTaskToWorker(new GeneratorUnregisterTask($this), $i); } } /** * @return BlockMetadataStore */ public function getBlockMetadata(): BlockMetadataStore { return $this->blockMetadata; } /** * @return Server */ public function getServer(): Server { return $this->server; } /** * @return LevelProvider */ final public function getProvider() { return $this->provider; } /** * Returns the unique level identifier * * @return int */ final public function getId(): int { return $this->levelId; } public function isClosed(): bool { return $this->closed; } public function close() { assert(!$this->closed, "Tried to close a level which is already closed"); if ($this->getAutoSave()) { $this->save(); } foreach ($this->chunks as $chunk) { $this->unloadChunk($chunk->getX(), $chunk->getZ(), false); } $this->unregisterGenerator(); $this->provider->close(); $this->provider = null; $this->blockMetadata = null; $this->blockCache = []; $this->temporalPosition = null; $this->closed = true; } public function addSound(Sound $sound, array $players = null) { $pk = $sound->encode(); if ($players === null) { if ($pk !== null) { if (!is_array($pk)) { $this->addChunkPacket($sound->x >> 4, $sound->z >> 4, $pk); } else { foreach ($pk as $e) { $this->addChunkPacket($sound->x >> 4, $sound->z >> 4, $e); } } } } else { if ($pk !== null) { if (!is_array($pk)) { $this->server->broadcastPacket($players, $pk); } else { $this->server->batchPackets($players, $pk, false); } } } } public function addParticle(Particle $particle, array $players = null) { $pk = $particle->encode(); if ($players === null) { if ($pk !== null) { if (!is_array($pk)) { $this->addChunkPacket($particle->x >> 4, $particle->z >> 4, $pk); } else { foreach ($pk as $e) { $this->addChunkPacket($particle->x >> 4, $particle->z >> 4, $e); } } } } else { if ($pk !== null) { if (!is_array($pk)) { $this->server->broadcastPacket($players, $pk); } else { $this->server->batchPackets($players, $pk, false); } } } } public function broadcastLevelEvent(Vector3 $pos, int $evid, int $data = 0) { $pk = new LevelEventPacket(); $pk->evid = $evid; $pk->data = $data; list($pk->x, $pk->y, $pk->z) = [$pos->x, $pos->y, $pos->z]; $this->addChunkPacket($pos->x >> 4, $pos->z >> 4, $pk); } public function broadcastLevelSoundEvent(Vector3 $pos, int $soundId, int $pitch = 1, int $extraData = -1) { $pk = new LevelSoundEventPacket(); $pk->sound = $soundId; $pk->pitch = $pitch; $pk->extraData = $extraData; list($pk->x, $pk->y, $pk->z) = [$pos->x, $pos->y, $pos->z]; $this->addChunkPacket($pos->x >> 4, $pos->z >> 4, $pk); } /** * @return bool */ public function getAutoSave(): bool { return $this->autoSave; } /** * @param bool $value */ public function setAutoSave(bool $value) { $this->autoSave = $value; } /** * Unloads the current level from memory safely * * @param bool $force default false, force unload of default level * * @return bool */ public function unload(bool $force = false): bool { $ev = new LevelUnloadEvent($this); if ($this === $this->server->getDefaultLevel() and $force !== true) { $ev->setCancelled(true); } $this->server->getPluginManager()->callEvent($ev); if (!$force and $ev->isCancelled()) { return false; } $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.level.unloading", [$this->getName()])); $defaultLevel = $this->server->getDefaultLevel(); foreach ($this->getPlayers() as $player) { if ($this === $defaultLevel or $defaultLevel === null) { $player->close($player->getLeaveMessage(), "Forced default level unload"); } elseif ($defaultLevel instanceof Level) { $player->teleport($this->server->getDefaultLevel()->getSafeSpawn()); } } if ($this === $defaultLevel) { $this->server->setDefaultLevel(null); } $this->close(); return true; } /** * Gets the players being used in a specific chunk * * @param int $chunkX * @param int $chunkZ * * @return Player[] */ public function getChunkPlayers(int $chunkX, int $chunkZ): array { return isset($this->playerLoaders[$index = Level::chunkHash($chunkX, $chunkZ)]) ? $this->playerLoaders[$index] : []; } /** * Gets the chunk loaders being used in a specific chunk * * @param int $chunkX * @param int $chunkZ * * @return ChunkLoader[] */ public function getChunkLoaders(int $chunkX, int $chunkZ): array { return isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)]) ? $this->chunkLoaders[$index] : []; } public function addChunkPacket(int $chunkX, int $chunkZ, DataPacket $packet) { if (!isset($this->chunkPackets[$index = Level::chunkHash($chunkX, $chunkZ)])) { $this->chunkPackets[$index] = [$packet]; } else { $this->chunkPackets[$index][] = $packet; } } public function registerChunkLoader(ChunkLoader $loader, int $chunkX, int $chunkZ, bool $autoLoad = true) { $hash = $loader->getLoaderId(); if (!isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)])) { $this->chunkLoaders[$index] = []; $this->playerLoaders[$index] = []; } elseif (isset($this->chunkLoaders[$index][$hash])) { return; } $this->chunkLoaders[$index][$hash] = $loader; if ($loader instanceof Player) { $this->playerLoaders[$index][$hash] = $loader; } if (!isset($this->loaders[$hash])) { $this->loaderCounter[$hash] = 1; $this->loaders[$hash] = $loader; } else { ++$this->loaderCounter[$hash]; } $this->cancelUnloadChunkRequest($chunkX, $chunkZ); if ($autoLoad) { $this->loadChunk($chunkX, $chunkZ); } } public function unregisterChunkLoader(ChunkLoader $loader, int $chunkX, int $chunkZ) { if (isset($this->chunkLoaders[$index = Level::chunkHash($chunkX, $chunkZ)][$hash = $loader->getLoaderId()])) { unset($this->chunkLoaders[$index][$hash]); unset($this->playerLoaders[$index][$hash]); if (count($this->chunkLoaders[$index]) === 0) { unset($this->chunkLoaders[$index]); unset($this->playerLoaders[$index]); $this->unloadChunkRequest($chunkX, $chunkZ, true); } if (--$this->loaderCounter[$hash] === 0) { unset($this->loaderCounter[$hash]); unset($this->loaders[$hash]); } } } /** * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. */ public function checkTime() { if ($this->stopTime == true) { return; } else { $this->time += 1; } } /** * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. */ public function sendTime() { $pk = new SetTimePacket(); $pk->time = (int)$this->time; $pk->started = $this->stopTime == false; $this->server->broadcastPacket($this->players, $pk); } /** * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. * * @param int $currentTick */ public function doTick(int $currentTick) { $this->timings->doTick->startTiming(); $this->checkTime(); if (++$this->sendTimeTicker === 280) { $this->sendTime(); $this->sendTimeTicker = 0; } $this->weather->calcWeather($currentTick); $this->unloadChunks(); //Do block updates $this->timings->doTickPending->startTiming(); if ($this->updateQueue->count() > 0 and $this->updateQueue->current()["priority"] <= $currentTick) { $block = $this->getBlock($this->updateQueue->extract()["data"]); unset($this->updateQueueIndex[Level::blockHash($block->x, $block->y, $block->z)]); $block->onUpdate(self::BLOCK_UPDATE_SCHEDULED); } $this->timings->doTickPending->stopTiming(); $this->timings->entityTick->startTiming(); //Update entities that need update Timings::$tickEntityTimer->startTiming(); foreach ($this->updateEntities as $id => $entity) { if ($entity->closed or !$entity->onUpdate($currentTick)) { unset($this->updateEntities[$id]); } } Timings::$tickEntityTimer->stopTiming(); $this->timings->entityTick->stopTiming(); $this->timings->tileEntityTick->startTiming(); Timings::$tickTileEntityTimer->startTiming(); //Update tiles that need update if (count($this->updateTiles) > 0) { foreach ($this->updateTiles as $id => $tile) { if ($tile->onUpdate() !== true) { unset($this->updateTiles[$id]); } } } Timings::$tickTileEntityTimer->stopTiming(); $this->timings->tileEntityTick->stopTiming(); $this->timings->doTickTiles->startTiming(); if (($currentTick % 2) === 0) $this->tickChunks(); $this->timings->doTickTiles->stopTiming(); if (count($this->changedBlocks) > 0) { if (count($this->players) > 0) { foreach ($this->changedBlocks as $index => $blocks) { unset($this->chunkCache[$index]); Level::getXZ($index, $chunkX, $chunkZ); if (count($blocks) > 512) { $chunk = $this->getChunk($chunkX, $chunkZ); foreach ($this->getChunkPlayers($chunkX, $chunkZ) as $p) { $p->onChunkChanged($chunk); } } else { $this->sendBlocks($this->getChunkPlayers($chunkX, $chunkZ), $blocks, UpdateBlockPacket::FLAG_ALL); } } } else { $this->chunkCache = []; } $this->changedBlocks = []; } $this->processChunkRequest(); if ($this->sleepTicks > 0 and --$this->sleepTicks <= 0) { $this->checkSleep(); } foreach ($this->moveToSend as $index => $entry) { Level::getXZ($index, $chunkX, $chunkZ); foreach ($entry as $e) { $pk = new MoveEntityPacket(); $pk->eid = $e[0]; $pk->x = $e[1]; $pk->y = $e[2]; $pk->z = $e[3]; $pk->yaw = $e[4]; $pk->headYaw = $e[5]; $pk->pitch = $e[6]; $this->addChunkPacket($chunkX, $chunkZ, $pk); } } $this->moveToSend = []; foreach ($this->motionToSend as $index => $entry) { Level::getXZ($index, $chunkX, $chunkZ); foreach ($entry as $entity) { $pk = new SetEntityMotionPacket(); $pk->eid = $entity[0]; $pk->motionX = $entity[1]; $pk->motionY = $entity[2]; $pk->motionZ = $entity[3]; $this->addChunkPacket($chunkX, $chunkZ, $pk); } } $this->motionToSend = []; foreach ($this->chunkPackets as $index => $entries) { Level::getXZ($index, $chunkX, $chunkZ); $chunkPlayers = $this->getChunkPlayers($chunkX, $chunkZ); if (count($chunkPlayers) > 0) { foreach ($entries as $pk) { $this->server->broadcastPacket($chunkPlayers, $pk); } } } $this->chunkPackets = []; $this->timings->doTick->stopTiming(); } public function checkSleep() { if (count($this->players) === 0) { return; } $resetTime = true; foreach ($this->getPlayers() as $p) { if (!$p->isSleeping()) { $resetTime = false; break; } } if ($resetTime) { $time = $this->getTime() % Level::TIME_FULL; if ($time >= Level::TIME_NIGHT and $time < Level::TIME_SUNRISE) { $this->setTime($this->getTime() + Level::TIME_FULL - $time); foreach ($this->getPlayers() as $p) { $p->stopSleep(); } } } } public function sendBlockExtraData(int $x, int $y, int $z, int $id, int $data, array $targets = null) { $pk = new LevelEventPacket; $pk->evid = LevelEventPacket::EVENT_SET_DATA; $pk->x = $x + 0.5; $pk->y = $y + 0.5; $pk->z = $z + 0.5; $pk->data = ($data << 8) | $id; $this->server->broadcastPacket($targets === null ? $this->getChunkPlayers($x >> 4, $z >> 4) : $targets, $pk); } /** * @param Player[] $target * @param Block[] $blocks * @param int $flags * @param bool $optimizeRebuilds */ public function sendBlocks(array $target, array $blocks, $flags = UpdateBlockPacket::FLAG_NONE, bool $optimizeRebuilds = false) { if ($optimizeRebuilds) { $chunks = []; foreach ($blocks as $b) { if ($b === null) { continue; } $pk = new UpdateBlockPacket(); $first = false; if (!isset($chunks[$index = Level::chunkHash($b->x >> 4, $b->z >> 4)])) { $chunks[$index] = true; $first = true; } $pk->x = $b->x; $pk->z = $b->z; $pk->y = $b->y; if ($b instanceof Block) { $pk->blockId = $b->getId(); $pk->blockData = $b->getDamage(); } else { $fullBlock = $this->getFullBlock($b->x, $b->y, $b->z); $pk->blockId = $fullBlock >> 4; $pk->blockData = $fullBlock & 0xf; } $pk->flags = $first ? $flags : UpdateBlockPacket::FLAG_NONE; $this->server->broadcastPacket($target, $pk); } } else { foreach ($blocks as $b) { if ($b === null) { continue; } $pk = new UpdateBlockPacket(); $pk->x = $b->x; $pk->z = $b->z; $pk->y = $b->y; if ($b instanceof Block) { $pk->blockId = $b->getId(); $pk->blockData = $b->getDamage(); } else { $fullBlock = $this->getFullBlock($b->x, $b->y, $b->z); $pk->blockId = $fullBlock >> 4; $pk->blockData = $fullBlock & 0xf; } $pk->flags = $flags; $this->server->broadcastPacket($target, $pk); } } } public function clearCache(bool $full = false) { if ($full) { $this->chunkCache = []; $this->blockCache = []; } else { if (count($this->chunkCache) > 768) { $this->chunkCache = []; } if (count($this->blockCache) > 2048) { $this->blockCache = []; } } } public function clearChunkCache(int $chunkX, int $chunkZ) { unset($this->chunkCache[Level::chunkHash($chunkX, $chunkZ)]); } private function tickChunks() { if ($this->chunksPerTick <= 0 or count($this->loaders) === 0) { $this->chunkTickList = []; return; } $chunksPerLoader = min(200, max(1, (int)((($this->chunksPerTick - count($this->loaders)) / count($this->loaders)) + 0.5))); $randRange = 3 + $chunksPerLoader / 30; $randRange = (int)($randRange > $this->chunkTickRadius ? $this->chunkTickRadius : $randRange); foreach ($this->loaders as $loader) { $chunkX = $loader->getX() >> 4; $chunkZ = $loader->getZ() >> 4; $index = Level::chunkHash($chunkX, $chunkZ); $existingLoaders = max(0, isset($this->chunkTickList[$index]) ? $this->chunkTickList[$index] : 0); $this->chunkTickList[$index] = $existingLoaders + 1; for ($chunk = 0; $chunk < $chunksPerLoader; ++$chunk) { $dx = mt_rand(-$randRange, $randRange); $dz = mt_rand(-$randRange, $randRange); $hash = Level::chunkHash($dx + $chunkX, $dz + $chunkZ); if (!isset($this->chunkTickList[$hash]) and isset($this->chunks[$hash])) { $this->chunkTickList[$hash] = -1; } } } foreach ($this->chunkTickList as $index => $loaders) { Level::getXZ($index, $chunkX, $chunkZ); if (!isset($this->chunks[$index]) or ($chunk = $this->getChunk($chunkX, $chunkZ, false)) === null) { unset($this->chunkTickList[$index]); continue; } elseif ($loaders <= 0) { unset($this->chunkTickList[$index]); } foreach ($chunk->getEntities() as $entity) { $entity->scheduleUpdate(); } foreach ($chunk->getSubChunks() as $Y => $subChunk) { if (!$subChunk->isEmpty()) { $k = mt_rand(0, 0x7fffffff); for ($i = 0; $i < 3; ++$i, $k >>= 10) { $x = $k & 0x0f; $y = ($k >> 8) & 0x0f; $z = ($k >> 16) & 0x0f; $blockId = $subChunk->getBlockId($x, $y, $z); if (isset($this->randomTickBlocks[$blockId])) { $class = $this->randomTickBlocks[$blockId]; /** @var Block $block */ $block = new $class($subChunk->getBlockData($x, $y, $z)); $block->x = $chunkX * 16 + $x; $block->y = ($Y << 4) + $y; $block->z = $chunkZ * 16 + $z; $block->level = $this; $block->onUpdate(self::BLOCK_UPDATE_RANDOM); } } } } } if ($this->clearChunksOnTick) { $this->chunkTickList = []; } } public function __debugInfo(): array { return []; } /** * @param bool $force * * @return bool */ public function save(bool $force = false): bool { if (!$this->getAutoSave() and !$force) { return false; } $this->server->getPluginManager()->callEvent(new LevelSaveEvent($this)); $this->provider->setTime((int)$this->time); $this->saveChunks(); if ($this->provider instanceof BaseLevelProvider) { $this->provider->saveLevelData(); } return true; } public function saveChunks() { foreach ($this->chunks as $chunk) { if ($chunk->hasChanged() and $chunk->isGenerated()) { $this->provider->setChunk($chunk->getX(), $chunk->getZ(), $chunk); $this->provider->saveChunk($chunk->getX(), $chunk->getZ()); $chunk->setChanged(false); } } } /** * @param Vector3 $pos */ public function updateAround(Vector3 $pos) { $pos = $pos->floor(); $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y - 1, $pos->z)))); if (!$ev->isCancelled()) { $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL); } $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y + 1, $pos->z)))); if (!$ev->isCancelled()) { $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL); } $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x - 1, $pos->y, $pos->z)))); if (!$ev->isCancelled()) { $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL); } $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x + 1, $pos->y, $pos->z)))); if (!$ev->isCancelled()) { $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL); } $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y, $pos->z - 1)))); if (!$ev->isCancelled()) { $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL); } $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($this->getBlock($this->temporalVector->setComponents($pos->x, $pos->y, $pos->z + 1)))); if (!$ev->isCancelled()) { $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL); } } /** * @param Vector3 $pos * @param int $delay */ public function scheduleUpdate(Vector3 $pos, int $delay) { if (isset($this->updateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->updateQueueIndex[$index] <= $delay) { return; } $this->updateQueueIndex[$index] = $delay; $this->updateQueue->insert(new Vector3((int)$pos->x, (int)$pos->y, (int)$pos->z), (int)$delay + $this->server->getTick()); } /** * @param AxisAlignedBB $bb * @param bool $targetFirst * * @return Block[] */ public function getCollisionBlocks(AxisAlignedBB $bb, bool $targetFirst = false): array { $minX = Math::floorFloat($bb->minX); $minY = Math::floorFloat($bb->minY); $minZ = Math::floorFloat($bb->minZ); $maxX = Math::ceilFloat($bb->maxX); $maxY = Math::ceilFloat($bb->maxY); $maxZ = Math::ceilFloat($bb->maxZ); $collides = []; if ($targetFirst) { for ($z = $minZ; $z <= $maxZ; ++$z) { for ($x = $minX; $x <= $maxX; ++$x) { for ($y = $minY; $y <= $maxY; ++$y) { $block = $this->getBlock($this->temporalVector->setComponents($x, $y, $z)); if ($block->getId() !== 0 and $block->collidesWithBB($bb)) { return [$block]; } } } } } else { for ($z = $minZ; $z <= $maxZ; ++$z) { for ($x = $minX; $x <= $maxX; ++$x) { for ($y = $minY; $y <= $maxY; ++$y) { $block = $this->getBlock($this->temporalVector->setComponents($x, $y, $z)); if ($block->getId() !== 0 and $block->collidesWithBB($bb)) { $collides[] = $block; } } } } } return $collides; } /** * @param Vector3 $pos * * @return bool */ public function isFullBlock(Vector3 $pos): bool { if ($pos instanceof Block) { if ($pos->isSolid()) { return true; } $bb = $pos->getBoundingBox(); } else { $bb = $this->getBlock($pos)->getBoundingBox(); } return $bb !== null and $bb->getAverageEdgeLength() >= 1; } /** * @param Entity $entity * @param AxisAlignedBB $bb * @param boolean $entities * * @return AxisAlignedBB[] */ public function getCollisionCubes(Entity $entity, AxisAlignedBB $bb, bool $entities = true): array { $minX = Math::floorFloat($bb->minX); $minY = Math::floorFloat($bb->minY); $minZ = Math::floorFloat($bb->minZ); $maxX = Math::ceilFloat($bb->maxX); $maxY = Math::ceilFloat($bb->maxY); $maxZ = Math::ceilFloat($bb->maxZ); $collides = []; for ($z = $minZ; $z <= $maxZ; ++$z) { for ($x = $minX; $x <= $maxX; ++$x) { for ($y = $minY; $y <= $maxY; ++$y) { $block = $this->getBlock($this->temporalVector->setComponents($x, $y, $z)); if (!$block->canPassThrough() and $block->collidesWithBB($bb)) { $collides[] = $block->getBoundingBox(); } } } } if ($entities) { foreach ($this->getCollidingEntities($bb->grow(0.25, 0.25, 0.25), $entity) as $ent) { $collides[] = clone $ent->boundingBox; } } return $collides; } /* public function rayTraceBlocks(Vector3 $pos1, Vector3 $pos2, $flag = false, $flag1 = false, $flag2 = false){ if(!is_nan($pos1->x) and !is_nan($pos1->y) and !is_nan($pos1->z)){ if(!is_nan($pos2->x) and !is_nan($pos2->y) and !is_nan($pos2->z)){ $x1 = (int) $pos1->x; $y1 = (int) $pos1->y; $z1 = (int) $pos1->z; $x2 = (int) $pos2->x; $y2 = (int) $pos2->y; $z2 = (int) $pos2->z; $block = $this->getBlock(Vector3::createVector($x1, $y1, $z1)); if(!$flag1 or $block->getBoundingBox() !== null){ $ob = $block->calculateIntercept($pos1, $pos2); if($ob !== null){ return $ob; } } $movingObjectPosition = null; $k = 200; while($k-- >= 0){ if(is_nan($pos1->x) or is_nan($pos1->y) or is_nan($pos1->z)){ return null; } if($x1 === $x2 and $y1 === $y2 and $z1 === $z2){ return $flag2 ? $movingObjectPosition : null; } $flag3 = true; $flag4 = true; $flag5 = true; $i = 999; $j = 999; $k = 999; if($x1 > $x2){ $i = $x2 + 1; }elseif($x1 < $x2){ $i = $x2; }else{ $flag3 = false; } if($y1 > $y2){ $j = $y2 + 1; }elseif($y1 < $y2){ $j = $y2; }else{ $flag4 = false; } if($z1 > $z2){ $k = $z2 + 1; }elseif($z1 < $z2){ $k = $z2; }else{ $flag5 = false; } //TODO } } } } */ public function getFullLight(Vector3 $pos): int { $chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false); $level = 0; if ($chunk !== null) { $level = $chunk->getBlockSkyLight($pos->x & 0x0f, $pos->y, $pos->z & 0x0f); //TODO: decrease light level by time of day if ($level < 15) { $level = $chunk->getBlockSkyLight($pos->x & 0x0f, $pos->y, $pos->z & 0x0f); } } return $level; } /** * @param $x * @param $y * @param $z * * @return int bitmap, (id << 4) | data */ public function getFullBlock(int $x, int $y, int $z): int { return $this->getChunk($x >> 4, $z >> 4, false)->getFullBlock($x & 0x0f, $y, $z & 0x0f); } /** * Gets the Block object on the Vector3 location * * @param Vector3 $pos * @param boolean $cached * * @return Block */ public function getBlock(Vector3 $pos, $cached = true): Block { $pos = $pos->floor(); $index = Level::blockHash($pos->x, $pos->y, $pos->z); if ($cached and isset($this->blockCache[$index])) { return $this->blockCache[$index]; } elseif ($pos->y >= 0 and $pos->y < $this->provider->getWorldHeight() and isset($this->chunks[$chunkIndex = Level::chunkHash($pos->x >> 4, $pos->z >> 4)])) { $fullState = $this->chunks[$chunkIndex]->getFullBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f); } else { $fullState = 0; } $block = clone $this->blockStates[$fullState & 0xfff]; $block->x = $pos->x; $block->y = $pos->y; $block->z = $pos->z; $block->level = $this; return $this->blockCache[$index] = $block; } public function updateAllLight(Vector3 $pos) { $this->updateBlockSkyLight($pos->x, $pos->y, $pos->z); $this->updateBlockLight($pos->x, $pos->y, $pos->z); } public function updateBlockSkyLight(int $x, int $y, int $z) { $this->timings->doBlockSkyLightUpdates->startTiming(); $oldHeightMap = $this->getHeightMap($x, $z); $sourceId = $this->getBlockIdAt($x, $y, $z); $yPlusOne = $y + 1; if ($yPlusOne === $oldHeightMap) { //Block changed directly beneath the heightmap. Check if a block was removed or changed to a different light-filter. $newHeightMap = $this->getChunk($x >> 4, $z >> 4)->recalculateHeightMapColumn($x & 0x0f, $z & 0x0f); } elseif ($yPlusOne > $oldHeightMap) { //Block **placed** above the heightmap. $this->setHeightMap($x, $z, $yPlusOne); $newHeightMap = $yPlusOne; } else { //block changed below heightmap $newHeightMap = $oldHeightMap; } $update = new SkyLightUpdate($this); if ($newHeightMap > $oldHeightMap) { //Heightmap increase, block placed, remove sky light for ($i = $y; $i >= $oldHeightMap; --$i) { $update->setAndUpdateLight($x, $i, $z, 0); //Remove all light beneath, adjacent recalculation will handle the rest. } } elseif ($newHeightMap < $oldHeightMap) { //Heightmap decrease, block changed or removed, add sky light for ($i = $y; $i >= $newHeightMap; --$i) { $update->setAndUpdateLight($x, $i, $z, 15); } } else { //No heightmap change, block changed "underground" $update->setAndUpdateLight($x, $y, $z, max(0, $this->getHighestAdjacentBlockLight($x, $y, $z) - Block::$lightFilter[$sourceId])); } $update->execute(); $this->timings->doBlockSkyLightUpdates->stopTiming(); } public function getHighestAdjacentBlockLight(int $x, int $y, int $z): int { return max([ $this->getBlockLightAt($x + 1, $y, $z), $this->getBlockLightAt($x - 1, $y, $z), $this->getBlockLightAt($x, $y + 1, $z), $this->getBlockLightAt($x, $y - 1, $z), $this->getBlockLightAt($x, $y, $z + 1), $this->getBlockLightAt($x, $y, $z - 1) ]); } public function updateBlockLight(int $x, int $y, int $z) { $this->timings->doBlockLightUpdates->startTiming(); $id = $this->getBlockIdAt($x, $y, $z); $newLevel = max(Block::$light[$id], $this->getHighestAdjacentBlockLight($x, $y, $z) - Block::$lightFilter[$id]); $update = new BlockLightUpdate($this); $update->setAndUpdateLight($x, $y, $z, $newLevel); $update->execute(); $this->timings->doBlockLightUpdates->stopTiming(); } //unused! /*private function computeRemoveBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, \SplQueue $spreadQueue, array &$visited, array &$spreadVisited) { if ($y < 0) return; $current = $this->getBlockLightAt($x, $y, $z); if ($current !== 0 and $current < $currentLight) { $this->setBlockLightAt($x, $y, $z, 0); if (!isset($visited[$index = Level::blockHash($x, $y, $z)])) { $visited[$index] = true; if ($current > 1) { $queue->enqueue([new Vector3($x, $y, $z), $current]); } } } elseif ($current >= $currentLight) { if (!isset($spreadVisited[$index = Level::blockHash($x, $y, $z)])) { $spreadVisited[$index] = true; $spreadQueue->enqueue(new Vector3($x, $y, $z)); } } } private function computeSpreadBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, array &$visited) { if ($y < 0) return; $current = $this->getBlockLightAt($x, $y, $z); $currentLight -= Block::$lightFilter[$this->getBlockIdAt($x, $y, $z)]; if ($current < $currentLight) { $this->setBlockLightAt($x, $y, $z, $currentLight); if (!isset($visited[$index = Level::blockHash($x, $y, $z)])) { $visited[$index] = true; if ($currentLight > 1) { $queue->enqueue(new Vector3($x, $y, $z)); } } } }*/ /** * Sets on Vector3 the data from a Block object, * does block updates and puts the changes to the send queue. * * If $direct is true, it'll send changes directly to players. if false, it'll be queued * and the best way to send queued changes will be done in the next tick. * This way big changes can be sent on a single chunk update packet instead of thousands of packets. * * If $update is true, it'll get the neighbour blocks (6 sides) and update them. * If you are doing big changes, you might want to set this to false, then update manually. * * @param Vector3 $pos * @param Block $block * @param bool $direct @deprecated * @param bool $update * * @return bool Whether the block has been updated or not */ public function setBlock(Vector3 $pos, Block $block, bool $direct = false, bool $update = true): bool { $pos = $pos->floor(); if ($pos->y < 0 or $pos->y >= $this->provider->getWorldHeight()) { return false; } $this->timings->setBlock->startTiming(); if ($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f, $block->getId(), $block->getDamage())) { if (!($pos instanceof Position)) { $pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z); } $block->position($pos); unset($this->blockCache[Level::blockHash($pos->x, $pos->y, $pos->z)]); $index = Level::chunkHash($pos->x >> 4, $pos->z >> 4); if ($direct === true) { $this->sendBlocks($this->getChunkPlayers($pos->x >> 4, $pos->z >> 4), [$block], UpdateBlockPacket::FLAG_ALL_PRIORITY); unset($this->chunkCache[$index]); } else { if (!isset($this->changedBlocks[$index])) { $this->changedBlocks[$index] = []; } $this->changedBlocks[$index][Level::blockHash($block->x, $block->y, $block->z)] = clone $block; } foreach ($this->getChunkLoaders($pos->x >> 4, $pos->z >> 4) as $loader) { $loader->onBlockChanged($block); } if ($update === true) { $this->updateAllLight($block); $this->server->getPluginManager()->callEvent($ev = new BlockUpdateEvent($block)); if (!$ev->isCancelled()) { foreach ($this->getNearbyEntities(new AxisAlignedBB($block->x - 1, $block->y - 1, $block->z - 1, $block->x + 1, $block->y + 1, $block->z + 1)) as $entity) { $entity->scheduleUpdate(); } $ev->getBlock()->onUpdate(self::BLOCK_UPDATE_NORMAL); } $this->updateAround($pos); } $this->timings->setBlock->stopTiming(); return true; } $this->timings->setBlock->stopTiming(); return false; } /** * @param Vector3 $source * @param Item $item * @param Vector3 $motion * @param int $delay * * @return null|Entity|DroppedItem|\pocketmine\entity\Projectile */ public function dropItem(Vector3 $source, Item $item, Vector3 $motion = null, int $delay = 10) { $motion = $motion === null ? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1) : $motion; if ($item->getId() > 0 and $item->getCount() > 0) { $itemEntity = Entity::createEntity("Item", $this, new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $source->getX()), new DoubleTag("", $source->getY()), new DoubleTag("", $source->getZ()) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", $motion->x), new DoubleTag("", $motion->y), new DoubleTag("", $motion->z) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", lcg_value() * 360), new FloatTag("", 0) ]), "Health" => new ShortTag("Health", 5), "Item" => $item->nbtSerialize(-1, "Item"), "PickupDelay" => new ShortTag("PickupDelay", $delay) ])); $itemEntity->spawnToAll(); return $itemEntity; } return null; } /** * Tries to break a block using a item, including Player time checks if available * It'll try to lower the durability if Item is a tool, and set it to Air if broken. * * @param Vector3 $vector * @param Item &$item (if null, can break anything) * @param Player $player * @param bool $createParticles * * @return bool */ public function useBreakOn(Vector3 $vector, Item &$item = null, Player $player = null, bool $createParticles = false): bool { $target = $this->getBlock($vector); if ($item === null) { $item = Item::get(Item::AIR, 0, 0); } if ($player !== null) { $ev = new BlockBreakEvent($player, $target, $item, ($player->isCreative() or $player->allowInstaBreak())); if ($player->isAdventure() or $player->isSpectator() or ($player->isSurvival() and $item instanceof Item and !$target->isBreakable($item))) { $ev->setCancelled(); } elseif (!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1) { $t = new Vector2($target->x, $target->z); $s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z); if (count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance) { //set it to cancelled so plugins can bypass this $ev->setCancelled(); } } $this->server->getPluginManager()->callEvent($ev); if ($ev->isCancelled()) { return false; } $breakTime = ceil($target->getBreakTime($item) * 20); if ($player->isCreative() and $breakTime > 3) { $breakTime = 3; } if ($player->hasEffect(Effect::SWIFTNESS)) { $breakTime *= 1 - (0.2 * ($player->getEffect(Effect::SWIFTNESS)->getAmplifier() + 1)); } if ($player->hasEffect(Effect::MINING_FATIGUE)) { $breakTime *= 1 + (0.3 * ($player->getEffect(Effect::MINING_FATIGUE)->getAmplifier() + 1)); } $breakTime -= 1; //1 tick compensation if (!$ev->getInstaBreak() and ((ceil($player->lastBreak * 20)) + $breakTime) > ceil(microtime(true) * 20)) { return false; } $player->lastBreak = PHP_INT_MAX; $drops = $ev->getDrops(); if ($player->isSurvival() and $this->getServer()->expEnabled) { $exp = 0; if ($item->getEnchantmentLevel(Enchantment::TYPE_MINING_SILK_TOUCH) === 0) { switch ($target->getId()) { case Block::COAL_ORE: $exp = mt_rand(0, 2); break; case Block::DIAMOND_ORE: case Block::EMERALD_ORE: $exp = mt_rand(3, 7); break; case Block::NETHER_QUARTZ_ORE: case Block::LAPIS_ORE: $exp = mt_rand(2, 5); break; case Block::REDSTONE_ORE: case Block::GLOWING_REDSTONE_ORE: $exp = mt_rand(1, 5); break; } } switch ($target->getId()) { case Block::MONSTER_SPAWNER: $exp = mt_rand(15, 43); break; } if ($exp > 0) { $this->spawnXPOrb($vector->add(0, 1, 0), $exp); } } } elseif ($item !== null and !$target->isBreakable($item)) { return false; } else { $drops = $target->getDrops($item); //Fixes tile entities being deleted before getting drops foreach ($drops as $k => $i) { if ((isset ($i[0])) && (isset ($i[1])) && (isset ($i[2]))) $drops[$k] = Item::get($i[0], $i[1], $i[2]); } } $above = $this->getBlock(new Vector3($target->x, $target->y + 1, $target->z)); if ($above !== null) { if ($above->getId() === Item::FIRE) { $this->setBlock($above, new Air(), true); } } $tag = $item->getNamedTagEntry("CanDestroy"); if ($tag instanceof ListTag) { $canBreak = false; foreach ($tag as $v) { if ($v instanceof StringTag) { $entry = Item::fromString($v->getValue()); if ($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()) { $canBreak = true; break; } } } if (!$canBreak) { return false; } } if ($createParticles) { $this->addParticle(new DestroyBlockParticle($target, $target)); } $target->onBreak($item); $tile = $this->getTile($target); if ($tile !== null) { if ($tile instanceof InventoryHolder) { if ($tile instanceof Chest) { $tile->unpair(); } foreach ($tile->getInventory()->getContents() as $chestItem) { $this->dropItem($target, $chestItem); } } $tile->close(); } if ($item !== null) { $item->useOn($target); if ($item->isTool() and $item->getDamage() >= $item->getMaxDurability()) { $item = Item::get(Item::AIR, 0, 0); } } if ($player === null or $player->isSurvival()) { foreach ($drops as $drop) { if ($drop->getCount() > 0) { $this->dropItem($vector->add(0.5, 0.5, 0.5), $drop); } } } return true; } /** * Uses a item on a position and face, placing it or activating the block * * @param Vector3 $vector * @param Item $item * @param int $face * @param float $fx default 0.0 * @param float $fy default 0.0 * @param float $fz default 0.0 * @param Player $player default null * * @return bool */ public function useItemOn(Vector3 $vector, Item &$item, int $face, float $fx = 0.0, float $fy = 0.0, float $fz = 0.0, Player $player = null): bool { $target = $this->getBlock($vector); $block = $target->getSide($face); if ($block->y >= $this->provider->getWorldHeight() or $block->y < 0) { //TODO: build height limit messages for custom world heights and mcregion cap return false; } if ($target->getId() === Item::AIR) { return false; } if ($player !== null) { $ev = new PlayerInteractEvent($player, $item, $target, $face, $target->getId() === 0 ? PlayerInteractEvent::RIGHT_CLICK_AIR : PlayerInteractEvent::RIGHT_CLICK_BLOCK); if (!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1) { $t = new Vector2($target->x, $target->z); $s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z); if (count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance) { //set it to cancelled so plugins can bypass this $ev->setCancelled(); } } if ($player->isSpectator()) { $ev->setCancelled(); } $this->server->getPluginManager()->callEvent($ev); if (!$ev->isCancelled()) { $target->onUpdate(self::BLOCK_UPDATE_TOUCH); if (!$player->isSneaking()) { if ($target->canBeActivated() === true and $target->onActivate($item, $player) === true) { if ($item->getCount() <= 0) { $item = Item::get(Item::AIR, 0, 0); } elseif ($item->isTool() and $item->getDamage() >= $item->getMaxDurability()) { $item = Item::get(Item::AIR, 0, 0); } return true; } if ($item->canBeActivated() and $item->onActivate($this, $player, $block, $target, $face, $fx, $fy, $fz)) { if ($item->getCount() <= 0) { $item = Item::get(Item::AIR, 0, 0); return true; } elseif ($item->isTool() and $item->getDamage() >= $item->getMaxDurability()) { $item = Item::get(Item::AIR, 0, 0); return true; } } } /*if(!$player->isSneaking() and $target->canBeActivated() === true and $target->onActivate($item, $player) === true){ return true; } if(!$player->isSneaking() and $item->canBeActivated() and $item->onActivate($this, $player, $block, $target, $face, $fx, $fy, $fz)){ if($item->getCount() <= 0){ $item = Item::get(Item::AIR, 0, 0); return true; } }*/ } else { return false; } } elseif ($target->canBeActivated() === true and $target->onActivate($item, $player) === true) { return true; } if ($item->canBePlaced()) { $hand = $item->getBlock(); $hand->position($block); } else { return false; } if (!($block->canBeReplaced() === true or ($hand->getId() === Item::SLAB and $block->getId() === Item::SLAB))) { return false; } if ($target->canBeReplaced() === true) { $block = $target; $hand->position($block); //$face = -1; } if ($hand->isSolid() === true and $hand->getBoundingBox() !== null) { $entities = $this->getCollidingEntities($hand->getBoundingBox()); $realCount = 0; foreach ($entities as $e) { if ($e instanceof Arrow or $e instanceof DroppedItem or ($e instanceof Player and $e->isSpectator())) { continue; } ++$realCount; } if ($player !== null) { if (($diff = $player->getNextPosition()->subtract($player->getPosition())) and $diff->lengthSquared() > 0.00001) { $bb = $player->getBoundingBox()->getOffsetBoundingBox($diff->x, $diff->y, $diff->z); if ($hand->getBoundingBox()->intersectsWith($bb)) { ++$realCount; } } } if ($realCount > 0) { return false; //Entity in block } } $tag = $item->getNamedTagEntry("CanPlaceOn"); if ($tag instanceof ListTag) { $canPlace = false; foreach ($tag as $v) { if ($v instanceof StringTag) { $entry = Item::fromString($v->getValue()); if ($entry->getId() > 0 and $entry->getBlock() !== null and $entry->getBlock()->getId() === $target->getId()) { $canPlace = true; break; } } } if (!$canPlace) { return false; } } if ($player !== null) { $ev = new BlockPlaceEvent($player, $hand, $block, $target, $item); if (!$player->isOp() and ($distance = $this->server->getSpawnRadius()) > -1) { $t = new Vector2($target->x, $target->z); $s = new Vector2($this->getSpawnLocation()->x, $this->getSpawnLocation()->z); if (count($this->server->getOps()->getAll()) > 0 and $t->distance($s) <= $distance) { //set it to cancelled so plugins can bypass this $ev->setCancelled(); } } $this->server->getPluginManager()->callEvent($ev); if ($ev->isCancelled()) { return false; } $this->addSound(new BlockPlaceSound($hand)); } if ($hand->place($item, $block, $target, $face, $fx, $fy, $fz, $player) === false) { return false; } $item->setCount($item->getCount() - 1); if ($item->getCount() <= 0) { $item = Item::get(Item::AIR, 0, 0); } return true; } /** * @param int $entityId * * @return Entity */ public function getEntity(int $entityId) { return isset($this->entities[$entityId]) ? $this->entities[$entityId] : null; } /** * Gets the list of all the entities in this level * * @return Entity[] */ public function getEntities(): array { return $this->entities; } /** * Returns the entities colliding the current one inside the AxisAlignedBB * * @param AxisAlignedBB $bb * @param Entity $entity * * @return Entity[] */ public function getCollidingEntities(AxisAlignedBB $bb, Entity $entity = null): array { $nearby = []; if ($entity === null or $entity->canCollide) { $minX = Math::floorFloat(($bb->minX - 2) / 16); $maxX = Math::ceilFloat(($bb->maxX + 2) / 16); $minZ = Math::floorFloat(($bb->minZ - 2) / 16); $maxZ = Math::ceilFloat(($bb->maxZ + 2) / 16); for ($x = $minX; $x <= $maxX; ++$x) { for ($z = $minZ; $z <= $maxZ; ++$z) { foreach ($this->getChunkEntities($x, $z) as $ent) { if ($ent instanceof Player and $ent->isSpectator()) { continue; } if ($entity == null) { if ($ent->boundingBox->intersectsWith($bb)) { $nearby[] = $ent; } } elseif ($entity instanceof Entity and $ent !== $entity and $entity->canCollideWith($ent)) { if ($ent->boundingBox->intersectsWith($bb)) { $nearby[] = $ent; } } } } } } return $nearby; } /** * Returns the entities near the current one inside the AxisAlignedBB * * @param AxisAlignedBB $bb * @param Entity $entity * * @return Entity[] */ public function getNearbyEntities(AxisAlignedBB $bb, Entity $entity = null): array { $nearby = []; $minX = Math::floorFloat(($bb->minX - 2) / 16); $maxX = Math::ceilFloat(($bb->maxX + 2) / 16); $minZ = Math::floorFloat(($bb->minZ - 2) / 16); $maxZ = Math::ceilFloat(($bb->maxZ + 2) / 16); for ($x = $minX; $x <= $maxX; ++$x) { for ($z = $minZ; $z <= $maxZ; ++$z) { foreach ($this->getChunkEntities($x, $z) as $ent) { if ($ent instanceof Player and $ent->isSpectator()) { continue; } if ($ent !== $entity and $ent->boundingBox->intersectsWith($bb)) { $nearby[] = $ent; } } } } return $nearby; } public function getNearbyExperienceOrb(AxisAlignedBB $bb): array { $nearby = []; foreach ($this->getNearbyEntities($bb) as $entity) { if ($entity instanceof XPOrb) { $nearby[] = $entity; } } return $nearby; } /** * Returns a list of the Tile entities in this level * * @return Tile[] */ public function getTiles(): array { return $this->tiles; } /** * @param $tileId * * @return Tile */ public function getTileById(int $tileId) { return isset($this->tiles[$tileId]) ? $this->tiles[$tileId] : null; } /** * Returns a list of the players in this level * * @return Player[] */ public function getPlayers(): array { return $this->players; } /** * @return ChunkLoader[] */ public function getLoaders(): array { return $this->loaders; } /** * Returns the Tile in a position, or null if not found * * @param Vector3 $pos * * @return Tile */ public function getTile(Vector3 $pos) { $chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false); if ($chunk !== null) { return $chunk->getTile($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f); } return null; } /** * Returns a list of the entities on a given chunk * * @param int $X * @param int $Z * * @return Entity[] */ public function getChunkEntities($X, $Z): array { return ($chunk = $this->getChunk($X, $Z)) !== null ? $chunk->getEntities() : []; } /** * Gives a list of the Tile entities on a given chunk * * @param int $X * @param int $Z * * @return Tile[] */ public function getChunkTiles($X, $Z): array { return ($chunk = $this->getChunk($X, $Z)) !== null ? $chunk->getTiles() : []; } /** * Gets the raw block id. * * @param int $x * @param int $y * @param int $z * * @return int 0-255 */ public function getBlockIdAt(int $x, int $y, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getBlockId($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); } /** * Sets the raw block id. * * @param int $x * @param int $y * @param int $z * @param int $id 0-255 */ public function setBlockIdAt(int $x, int $y, int $z, int $id) { unset($this->blockCache[Level::blockHash($x, $y, $z)]); $this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $id & 0xff); if (!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])) { $this->changedBlocks[$index] = []; } $this->changedBlocks[$index][Level::blockHash($x, $y, $z)] = $v = new Vector3($x, $y, $z); foreach ($this->getChunkLoaders($x >> 4, $z >> 4) as $loader) { $loader->onBlockChanged($v); } } /** * Gets the raw block extra data * * @param int $x * @param int $y * @param int $z * * @return int 16-bit */ public function getBlockExtraDataAt(int $x, int $y, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getBlockExtraData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); } /** * Sets the raw block metadata. * * @param int $x * @param int $y * @param int $z * @param int $id * @param int $data */ public function setBlockExtraDataAt(int $x, int $y, int $z, int $id, int $data) { $this->getChunk($x >> 4, $z >> 4, true)->setBlockExtraData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, ($data << 8) | $id); $this->sendBlockExtraData($x, $y, $z, $id, $data); } /** * Gets the raw block metadata * * @param int $x * @param int $y * @param int $z * * @return int 0-15 */ public function getBlockDataAt(int $x, int $y, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getBlockData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); } /** * Sets the raw block metadata. * * @param int $x * @param int $y * @param int $z * @param int $data 0-15 */ public function setBlockDataAt(int $x, int $y, int $z, int $data) { unset($this->blockCache[Level::blockHash($x, $y, $z)]); $this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $data & 0x0f); if (!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])) { $this->changedBlocks[$index] = []; } $this->changedBlocks[$index][Level::blockHash($x, $y, $z)] = $v = new Vector3($x, $y, $z); foreach ($this->getChunkLoaders($x >> 4, $z >> 4) as $loader) { $loader->onBlockChanged($v); } } /** * Gets the raw block skylight level * * @param int $x * @param int $y * @param int $z * * @return int 0-15 */ public function getBlockSkyLightAt(int $x, int $y, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getBlockSkyLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); } /** * Sets the raw block skylight level. * * @param int $x * @param int $y * @param int $z * @param int $level 0-15 */ public function setBlockSkyLightAt(int $x, int $y, int $z, int $level) { $this->getChunk($x >> 4, $z >> 4, true)->setBlockSkyLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $level & 0x0f); } /** * Gets the raw block light level * * @param int $x * @param int $y * @param int $z * * @return int 0-15 */ public function getBlockLightAt(int $x, int $y, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getBlockLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); } /** * Sets the raw block light level. * * @param int $x * @param int $y * @param int $z * @param int $level 0-15 */ public function setBlockLightAt(int $x, int $y, int $z, int $level) { $this->getChunk($x >> 4, $z >> 4, true)->setBlockLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $level & 0x0f); } /** * @param int $x * @param int $z * * @return int */ public function getBiomeId(int $x, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getBiomeId($x & 0x0f, $z & 0x0f); } /** * @param int $x * @param int $z * * @return int */ public function getHeightMap(int $x, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getHeightMap($x & 0x0f, $z & 0x0f); } /** * @param int $x * @param int $z * @param int $biomeId */ public function setBiomeId(int $x, int $z, int $biomeId) { $this->getChunk($x >> 4, $z >> 4, true)->setBiomeId($x & 0x0f, $z & 0x0f, $biomeId); } /** * @param int $x * @param int $z * @param int $value */ public function setHeightMap(int $x, int $z, int $value) { $this->getChunk($x >> 4, $z >> 4, true)->setHeightMap($x & 0x0f, $z & 0x0f, $value); } /** * @return Chunk[] */ public function getChunks(): array { return $this->chunks; } /** * Gets the Chunk object * * @param int $x * @param int $z * @param bool $create Whether to generate the chunk if it does not exist * * @return Chunk */ public function getChunk(int $x, int $z, bool $create = false) { if (isset($this->chunks[$index = Level::chunkHash($x, $z)])) { return $this->chunks[$index]; } elseif ($this->loadChunk($x, $z, $create)) { return $this->chunks[$index]; } return null; } /** * Returns the chunks adjacent to the specified chunk. * * @param int $x * @param int $z * * @return Chunk[] */ public function getAdjacentChunks(int $x, int $z): array { $result = []; for ($xx = 0; $xx <= 2; ++$xx) { for ($zz = 0; $zz <= 2; ++$zz) { $i = $zz * 3 + $xx; if ($i === 4) { continue; //center chunk } $result[$i] = $this->getChunk($x + $xx - 1, $z + $zz - 1, false); } } return $result; } public function generateChunkCallback(int $x, int $z, Chunk $chunk) { Timings::$generationCallbackTimer->startTiming(); if (isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)])) { $oldChunk = $this->getChunk($x, $z, false); for ($xx = -1; $xx <= 1; ++$xx) { for ($zz = -1; $zz <= 1; ++$zz) { unset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)]); } } unset($this->chunkPopulationQueue[$index]); $this->setChunk($x, $z, $chunk, false); $chunk = $this->getChunk($x, $z, false); if ($chunk !== null and ($oldChunk === null or $oldChunk->isPopulated() === false) and $chunk->isPopulated()) { $this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($this, $chunk)); foreach ($this->getChunkLoaders($x, $z) as $loader) { $loader->onChunkPopulated($chunk); } } } elseif (isset($this->chunkGenerationQueue[$index]) or isset($this->chunkPopulationLock[$index])) { unset($this->chunkGenerationQueue[$index]); unset($this->chunkPopulationLock[$index]); $this->setChunk($x, $z, $chunk, false); } else { $this->setChunk($x, $z, $chunk, false); } Timings::$generationCallbackTimer->stopTiming(); } /** * @param int $chunkX * @param int $chunkZ * @param Chunk $chunk * @param bool $unload */ public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk = null, bool $unload = true) { if ($chunk === null) { return; } $index = Level::chunkHash($chunkX, $chunkZ); $oldChunk = $this->getChunk($chunkX, $chunkZ, false); if ($unload and $oldChunk !== null) { $this->unloadChunk($chunkX, $chunkZ, false, false); $this->provider->setChunk($chunkX, $chunkZ, $chunk); $this->chunks[$index] = $chunk; } else { $oldEntities = $oldChunk !== null ? $oldChunk->getEntities() : []; $oldTiles = $oldChunk !== null ? $oldChunk->getTiles() : []; $this->provider->setChunk($chunkX, $chunkZ, $chunk); $this->chunks[$index] = $chunk; foreach ($oldEntities as $entity) { $chunk->addEntity($entity); $entity->chunk = $chunk; } foreach ($oldTiles as $tile) { $chunk->addTile($tile); $tile->chunk = $chunk; } } unset($this->chunkCache[$index]); $chunk->setChanged(); if (!$this->isChunkInUse($chunkX, $chunkZ)) { $this->unloadChunkRequest($chunkX, $chunkZ); } else { foreach ($this->getChunkLoaders($chunkX, $chunkZ) as $loader) { $loader->onChunkChanged($chunk); } } } /** * Directly send a lightning to a player * * @deprecated * * @param int $x * @param int $y * @param int $z * @param Player $p */ public function sendLighting(int $x, int $y, int $z, Player $p) { $pk = new AddEntityPacket(); $pk->type = Lightning::NETWORK_ID; $pk->eid = mt_rand(10000000, 100000000); $pk->x = $x; $pk->y = $y; $pk->z = $z; $pk->metadata = array(3, 3, 3, 3); $p->dataPacket($pk); } /** * Add a lightning * * @param Vector3 $pos * @return Lightning */ public function spawnLightning(Vector3 $pos): Lightning { $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $pos->getX()), new DoubleTag("", $pos->getY()), new DoubleTag("", $pos->getZ()) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), ]); $lightning = new Lightning($this, $nbt); $lightning->spawnToAll(); return $lightning; } /** * Add an experience orb * * @param Vector3 $pos * @param int $exp * @return bool|XPOrb */ public function spawnXPOrb(Vector3 $pos, int $exp = 1) { if ($exp > 0) { $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $pos->getX()), new DoubleTag("", $pos->getY() + 0.5), new DoubleTag("", $pos->getZ()) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", 0), new FloatTag("", 0) ]), "Experience" => new LongTag("Experience", $exp), ]); $expOrb = new XPOrb($this, $nbt); $expOrb->spawnToAll(); return $expOrb; } return false; } /** * Gets the highest block Y value at a specific $x and $z * * @param int $x * @param int $z * * @return int 0-255 */ public function getHighestBlockAt(int $x, int $z): int { return $this->getChunk($x >> 4, $z >> 4, true)->getHighestBlockAt($x & 0x0f, $z & 0x0f); } public function canBlockSeeSky(Vector3 $pos): bool { return $this->getHighestBlockAt($pos->getFloorX(), $pos->getFloorZ()) < $pos->getY(); } /** * @param int $x * @param int $z * * @return bool */ public function isChunkLoaded(int $x, int $z): bool { return isset($this->chunks[Level::chunkHash($x, $z)]) or $this->provider->isChunkLoaded($x, $z); } /** * @param int $x * @param int $z * * @return bool */ public function isChunkGenerated(int $x, int $z): bool { $chunk = $this->getChunk($x, $z); return $chunk !== null ? $chunk->isGenerated() : false; } /** * @param int $x * @param int $z * * @return bool */ public function isChunkPopulated(int $x, int $z): bool { $chunk = $this->getChunk($x, $z); return $chunk !== null ? $chunk->isPopulated() : false; } /** * Returns a Position pointing to the spawn * * @return Position */ public function getSpawnLocation(): Position { return Position::fromObject($this->provider->getSpawn(), $this); } /** * Sets the level spawn location * * @param Vector3 $pos */ public function setSpawnLocation(Vector3 $pos) { $previousSpawn = $this->getSpawnLocation(); $this->provider->setSpawn($pos); $this->server->getPluginManager()->callEvent(new SpawnChangeEvent($this, $previousSpawn)); } public function requestChunk(int $x, int $z, Player $player) { $index = Level::chunkHash($x, $z); if (!isset($this->chunkSendQueue[$index])) { $this->chunkSendQueue[$index] = []; } $this->chunkSendQueue[$index][$player->getLoaderId()] = $player; } private function sendChunkFromCache($x, $z) { if (isset($this->chunkSendTasks[$index = Level::chunkHash($x, $z)])) { foreach ($this->chunkSendQueue[$index] as $player) { /** @var Player $player */ if ($player->isConnected() and isset($player->usedChunks[$index])) { $player->sendChunk($x, $z, $this->chunkCache[$index]); } } unset($this->chunkSendQueue[$index]); unset($this->chunkSendTasks[$index]); } } private function processChunkRequest() { if (count($this->chunkSendQueue) > 0) { $this->timings->syncChunkSendTimer->startTiming(); $x = null; $z = null; foreach ($this->chunkSendQueue as $index => $players) { if (isset($this->chunkSendTasks[$index])) { continue; } Level::getXZ($index, $x, $z); $this->chunkSendTasks[$index] = true; if (isset($this->chunkCache[$index])) { $this->sendChunkFromCache($x, $z); continue; } $this->timings->syncChunkSendPrepareTimer->startTiming(); $task = $this->provider->requestChunkTask($x, $z); if ($task !== null) { $this->server->getScheduler()->scheduleAsyncTask($task); } $this->timings->syncChunkSendPrepareTimer->stopTiming(); } $this->timings->syncChunkSendTimer->stopTiming(); } } public function chunkRequestCallback(int $x, int $z, string $payload) { $this->timings->syncChunkSendTimer->startTiming(); $index = Level::chunkHash($x, $z); if (!isset($this->chunkCache[$index]) and $this->cacheChunks and $this->server->getMemoryManager()->canUseChunkCache()) { $this->chunkCache[$index] = Level::getChunkCacheFromData($x, $z, $payload); $this->sendChunkFromCache($x, $z); $this->timings->syncChunkSendTimer->stopTiming(); return; } if (isset($this->chunkSendTasks[$index])) { foreach ($this->chunkSendQueue[$index] as $player) { /** @var Player $player */ if ($player->isConnected() and isset($player->usedChunks[$index])) { $player->sendChunk($x, $z, $payload); } } unset($this->chunkSendQueue[$index]); unset($this->chunkSendTasks[$index]); } $this->timings->syncChunkSendTimer->stopTiming(); } /** * Removes the entity from the level index * * @param Entity $entity * * @throws LevelException */ public function removeEntity(Entity $entity) { if ($entity->getLevel() !== $this) { throw new LevelException("Invalid Entity level"); } if ($entity instanceof Player) { unset($this->players[$entity->getId()]); $this->checkSleep(); } else { $entity->close(); } unset($this->entities[$entity->getId()]); unset($this->updateEntities[$entity->getId()]); } /** * @param Entity $entity * * @throws LevelException */ public function addEntity(Entity $entity) { if ($entity->getLevel() !== $this) { throw new LevelException("Invalid Entity level"); } if ($entity instanceof Player) { $this->players[$entity->getId()] = $entity; } $this->entities[$entity->getId()] = $entity; } /** * @param Tile $tile * * @throws LevelException */ public function addTile(Tile $tile) { if ($tile->getLevel() !== $this) { throw new LevelException("Invalid Tile level"); } $this->tiles[$tile->getId()] = $tile; $this->clearChunkCache($tile->getX() >> 4, $tile->getZ() >> 4); } /** * @param Tile $tile * * @throws LevelException */ public function removeTile(Tile $tile) { if ($tile->getLevel() !== $this) { throw new LevelException("Invalid Tile level"); } unset($this->tiles[$tile->getId()]); unset($this->updateTiles[$tile->getId()]); $this->clearChunkCache($tile->getX() >> 4, $tile->getZ() >> 4); } /** * @param int $x * @param int $z * * @return bool */ public function isChunkInUse(int $x, int $z): bool { return isset($this->chunkLoaders[$index = Level::chunkHash($x, $z)]) and count($this->chunkLoaders[$index]) > 0; } /** * @param int $x * @param int $z * @param bool $generate * * @return bool */ public function loadChunk(int $x, int $z, bool $generate = true): bool { if (isset($this->chunks[$index = Level::chunkHash($x, $z)])) { return true; } $this->timings->syncChunkLoadTimer->startTiming(); $this->cancelUnloadChunkRequest($x, $z); $chunk = $this->provider->getChunk($x, $z, $generate); if ($chunk === null) { if ($generate) { throw new \InvalidStateException("Could not create new Chunk"); } return false; } $this->chunks[$index] = $chunk; $chunk->initChunk($this); $this->server->getPluginManager()->callEvent(new ChunkLoadEvent($this, $chunk, !$chunk->isGenerated())); if (!$chunk->isLightPopulated() and $chunk->isPopulated() and $this->getServer()->getProperty("chunk-ticking.light-updates", false)) { $this->getServer()->getScheduler()->scheduleAsyncTask(new LightPopulationTask($this, $chunk)); } if ($this->isChunkInUse($x, $z)) { foreach ($this->getChunkLoaders($x, $z) as $loader) { $loader->onChunkLoaded($chunk); } } else { $this->unloadChunkRequest($x, $z); } $this->timings->syncChunkLoadTimer->stopTiming(); return true; } private function queueUnloadChunk(int $x, int $z) { $this->unloadQueue[$index = Level::chunkHash($x, $z)] = microtime(true); unset($this->chunkTickList[$index]); } public function unloadChunkRequest(int $x, int $z, bool $safe = true): bool { if (($safe === true and $this->isChunkInUse($x, $z)) or $this->isSpawnChunk($x, $z)) { return false; } $this->queueUnloadChunk($x, $z); return true; } public function cancelUnloadChunkRequest(int $x, int $z) { unset($this->unloadQueue[Level::chunkHash($x, $z)]); } public function unloadChunk(int $x, int $z, bool $safe = true, bool $trySave = true): bool { if (($safe === true and $this->isChunkInUse($x, $z))) { return false; } if (!$this->isChunkLoaded($x, $z)) { return true; } $this->timings->doChunkUnload->startTiming(); $index = Level::chunkHash($x, $z); $chunk = $this->chunks[$index] ?? null;; if ($chunk !== null) { $this->server->getPluginManager()->callEvent($ev = new ChunkUnloadEvent($this, $chunk)); if ($ev->isCancelled()) { $this->timings->doChunkUnload->stopTiming(); return false; } } try { if ($chunk !== null) { if ($trySave and $this->getAutoSave() and $chunk->isGenerated()) { $entities = 0; foreach ($chunk->getEntities() as $e) { if ($e instanceof Player) { continue; } ++$entities; } if ($chunk->hasChanged() or count($chunk->getTiles()) > 0 or $entities > 0) { $this->provider->setChunk($x, $z, $chunk); $this->provider->saveChunk($x, $z); } } foreach ($this->getChunkLoaders($x, $z) as $loader) { $loader->onChunkUnloaded($chunk); } } $this->provider->unloadChunk($x, $z, $safe); } catch (\Throwable $e) { $logger = $this->server->getLogger(); $logger->error($this->server->getLanguage()->translateString("pocketmine.level.chunkUnloadError", [$e->getMessage()])); $logger->logException($e); } unset($this->chunks[$index]); unset($this->chunkTickList[$index]); unset($this->chunkCache[$index]); $this->timings->doChunkUnload->stopTiming(); return true; } /** * Returns true if the spawn is part of the spawn * * @param int $X * @param int $Z * * @return bool */ public function isSpawnChunk(int $X, int $Z): bool { $spawnX = $this->provider->getSpawn()->getX() >> 4; $spawnZ = $this->provider->getSpawn()->getZ() >> 4; return abs($X - $spawnX) <= 1 and abs($Z - $spawnZ) <= 1; } /** * @param Vector3 $spawn default null * * @return bool|Position */ public function getSafeSpawn($spawn = null) { if (!($spawn instanceof Vector3) or $spawn->y < 1) { $spawn = $this->getSpawnLocation(); } if ($spawn instanceof Vector3) { $max = $this->provider->getWorldHeight(); $v = $spawn->floor(); $chunk = $this->getChunk($v->x >> 4, $v->z >> 4, false); $x = $v->x & 0x0f; $z = $v->z & 0x0f; if ($chunk !== null) { $y = (int)min($max - 2, $v->y); $wasAir = ($chunk->getBlockId($x, $y - 1, $z) === 0); for (; $y > 0; --$y) { $b = $chunk->getFullBlock($x, $y, $z); $block = Block::get($b >> 4, $b & 0x0f); if ($this->isFullBlock($block)) { if ($wasAir) { $y++; break; } } else { $wasAir = true; } } for (; $y >= 0 and $y < $max; ++$y) { $b = $chunk->getFullBlock($x, $y + 1, $z); $block = Block::get($b >> 4, $b & 0x0f); if (!$this->isFullBlock($block)) { $b = $chunk->getFullBlock($x, $y, $z); $block = Block::get($b >> 4, $b & 0x0f); if (!$this->isFullBlock($block)) { return new Position($spawn->x, $y === (int)$spawn->y ? $spawn->y : $y, $spawn->z, $this); } } else { ++$y; } } $v->y = $y; } return new Position($spawn->x, $v->y, $spawn->z, $this); } return false; } /** * Gets the current time * * @return int */ public function getTime(): int { return $this->time; } /** * Returns the Level name * * @return string */ public function getName(): string { if ($this->provider !== null and $this->provider->getName() !== null) { return $this->provider->getName(); } else { return $this->getFolderName(); } } /** * Returns the Level folder name * * @return string */ public function getFolderName(): string { return $this->folderName; } /** * Sets the current time on the level * * @param int $time */ public function setTime(int $time) { $this->time = $time; $this->sendTime(); } /** * Stops the time for the level, will not save the lock state to disk */ public function stopTime() { $this->stopTime = true; $this->sendTime(); } /** * Start the time again, if it was stopped */ public function startTime() { $this->stopTime = false; $this->sendTime(); } /** * Gets the level seed * * @return int|string */ public function getSeed() { return $this->provider->getSeed(); } /** * Sets the seed for the level * * @param int $seed */ public function setSeed(int $seed) { $this->provider->setSeed($seed); } public function populateChunk(int $x, int $z, bool $force = false): bool { if (isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)) { return false; } $chunk = $this->getChunk($x, $z, true); if (!$chunk->isPopulated()) { Timings::$populationTimer->startTiming(); $populate = true; for ($xx = -1; $xx <= 1; ++$xx) { for ($zz = -1; $zz <= 1; ++$zz) { if (isset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)])) { $populate = false; break; } } } if ($populate) { if (!isset($this->chunkPopulationQueue[$index])) { $this->chunkPopulationQueue[$index] = true; for ($xx = -1; $xx <= 1; ++$xx) { for ($zz = -1; $zz <= 1; ++$zz) { $this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true; } } $task = new PopulationTask($this, $chunk); $this->server->getScheduler()->scheduleAsyncTask($task); } } Timings::$populationTimer->stopTiming(); return false; } return true; } public function generateChunk(int $x, int $z, bool $force = false) { if (count($this->chunkGenerationQueue) >= $this->chunkGenerationQueueSize and !$force) { return; } if (!isset($this->chunkGenerationQueue[$index = Level::chunkHash($x, $z)])) { Timings::$generationTimer->startTiming(); $this->chunkGenerationQueue[$index] = true; $task = new GenerationTask($this, $this->getChunk($x, $z, true)); $this->server->getScheduler()->scheduleAsyncTask($task); Timings::$generationTimer->stopTiming(); } } public function regenerateChunk(int $x, int $z) { $this->unloadChunk($x, $z, false); $this->cancelUnloadChunkRequest($x, $z); $this->generateChunk($x, $z); //TODO: generate & refresh chunk from the generator object } public function doChunkGarbageCollection() { $this->timings->doChunkGC->startTiming(); $X = null; $Z = null; foreach ($this->chunks as $index => $chunk) { if (!isset($this->unloadQueue[$index])) { Level::getXZ($index, $X, $Z); if (!$this->isSpawnChunk($X, $Z)) { $this->unloadChunkRequest($X, $Z, true); } } } foreach ($this->provider->getLoadedChunks() as $chunk) { if (!isset($this->chunks[Level::chunkHash($chunk->getX(), $chunk->getZ())])) { $this->provider->unloadChunk($chunk->getX(), $chunk->getZ(), false); } } $this->provider->doGarbageCollection(); $this->timings->doChunkGC->stopTiming(); } public function unloadChunks(bool $force = false) { if (!is_null($this->unloadQueue) && count($this->unloadQueue) > 0) { $maxUnload = 96; $now = microtime(true); foreach ($this->unloadQueue as $index => $time) { Level::getXZ($index, $X, $Z); if (!$force) { if ($maxUnload <= 0) { break; } elseif ($time > ($now - 30)) { continue; } } //If the chunk can't be unloaded, it stays on the queue if ($this->unloadChunk($X, $Z, true)) { unset($this->unloadQueue[$index]); --$maxUnload; } } } } /** * @param int $chunkX * @param int $chunkZ * @param string $payload * * @return DataPacket */ public static function getChunkCacheFromData($chunkX, $chunkZ, $payload) { $pk = new FullChunkDataPacket(); $pk->chunkX = $chunkX; $pk->chunkZ = $chunkZ; $pk->data = $payload; $pk->encode(); $batch = new BatchPacket(); $batch->payload = zlib_encode(Binary::writeUnsignedVarInt(strlen($pk->getBuffer())) . $pk->getBuffer(), ZLIB_ENCODING_DEFLATE, Server::getInstance()->networkCompressionLevel); $batch->encode(); $batch->isEncoded = true; return $batch; } public function setMetadata($metadataKey, MetadataValue $metadataValue) { $this->server->getLevelMetadata()->setMetadata($this, $metadataKey, $metadataValue); } public function getMetadata($metadataKey) { return $this->server->getLevelMetadata()->getMetadata($this, $metadataKey); } public function hasMetadata($metadataKey) { return $this->server->getLevelMetadata()->hasMetadata($this, $metadataKey); } public function removeMetadata($metadataKey, Plugin $plugin) { $this->server->getLevelMetadata()->removeMetadata($this, $metadataKey, $plugin); } public function addEntityMotion(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z) { if (!isset($this->motionToSend[$index = Level::chunkHash($chunkX, $chunkZ)])) { $this->motionToSend[$index] = []; } $this->motionToSend[$index][$entityId] = [$entityId, $x, $y, $z]; } public function addEntityMovement(int $chunkX, int $chunkZ, int $entityId, float $x, float $y, float $z, float $yaw, float $pitch, $headYaw = null) { if (!isset($this->moveToSend[$index = Level::chunkHash($chunkX, $chunkZ)])) { $this->moveToSend[$index] = []; } $this->moveToSend[$index][$entityId] = [$entityId, $x, $y, $z, $yaw, $headYaw === null ? $yaw : $headYaw, $pitch]; } } level = $level; $this->removalQueue = new \SplQueue(); $this->spreadQueue = new \SplQueue(); } public function addSpreadNode(int $x, int $y, int $z) { $this->spreadQueue->enqueue([$x, $y, $z]); } public function addRemoveNode(int $x, int $y, int $z, int $oldLight) { $this->spreadQueue->enqueue([$x, $y, $z, $oldLight]); } abstract protected function getLight(int $x, int $y, int $z): int; abstract protected function setLight(int $x, int $y, int $z, int $level); public function setAndUpdateLight(int $x, int $y, int $z, int $newLevel) { if (isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)]) or isset($this->removalVisited[$index])) { throw new \InvalidArgumentException("Already have a visit ready for this block"); } $oldLevel = $this->getLight($x, $y, $z); if ($oldLevel !== $newLevel) { $this->setLight($x, $y, $z, $newLevel); if ($oldLevel < $newLevel) { //light increased $this->spreadVisited[$index] = true; $this->spreadQueue->enqueue([$x, $y, $z]); } else { //light removed $this->removalVisited[$index] = true; $this->removalQueue->enqueue([$x, $y, $z, $oldLevel]); } } } public function execute() { while (!$this->removalQueue->isEmpty()) { list($x, $y, $z, $oldAdjacentLight) = $this->removalQueue->dequeue(); $points = [ [$x + 1, $y, $z], [$x - 1, $y, $z], [$x, $y + 1, $z], [$x, $y - 1, $z], [$x, $y, $z + 1], [$x, $y, $z - 1] ]; foreach ($points as list($cx, $cy, $cz)) { if ($cy < 0) { continue; } $this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight); } } while (!$this->spreadQueue->isEmpty()) { list($x, $y, $z) = $this->spreadQueue->dequeue(); $newAdjacentLight = $this->getLight($x, $y, $z); if ($newAdjacentLight <= 0) { continue; } $points = [ [$x + 1, $y, $z], [$x - 1, $y, $z], [$x, $y + 1, $z], [$x, $y - 1, $z], [$x, $y, $z + 1], [$x, $y, $z - 1] ]; foreach ($points as list($cx, $cy, $cz)) { if ($cy < 0) { continue; } $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight); } } } protected function computeRemoveLight(int $x, int $y, int $z, int $oldAdjacentLevel) { $current = $this->getLight($x, $y, $z); if ($current !== 0 and $current < $oldAdjacentLevel) { $this->setLight($x, $y, $z, 0); if (!isset($visited[$index = Level::blockHash($x, $y, $z)])) { $this->removalVisited[$index] = true; if ($current > 1) { $this->removalQueue->enqueue([$x, $y, $z, $current]); } } } elseif ($current >= $oldAdjacentLevel) { if (!isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)])) { $this->spreadVisited[$index] = true; $this->spreadQueue->enqueue([$x, $y, $z]); } } } protected function computeSpreadLight(int $x, int $y, int $z, int $newAdjacentLevel) { $current = $this->getLight($x, $y, $z); $potentialLight = $newAdjacentLevel - Block::$lightFilter[$this->level->getBlockIdAt($x, $y, $z)]; if ($current < $potentialLight) { $this->setLight($x, $y, $z, $potentialLight); if (!isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)])) { $this->spreadVisited[$index] = true; if ($potentialLight > 1) { $this->spreadQueue->enqueue([$x, $y, $z]); } } } } }x = $x; $this->y = $y; $this->z = $z; $this->yaw = $yaw; $this->pitch = $pitch; $this->level = $level; } /** * @param Vector3 $pos * @param Level|null $level default null * @param float $yaw default 0.0 * @param float $pitch default 0.0 * * @return Location */ public static function fromObject(Vector3 $pos, Level $level = null, $yaw = 0.0, $pitch = 0.0){ return new Location($pos->x, $pos->y, $pos->z, $yaw, $pitch, ($level === null) ? (($pos instanceof Position) ? $pos->level : null) : $level); } /** * Return a Location instance * * @return Location */ public function asLocation() : Location{ return new Location($this->x, $this->y, $this->z, $this->yaw, $this->pitch, $this->level); } /** * @param $x * @param int $y * @param int $z * @param int $yaw * @param int $pitch * * @return Location */ public function add($x, $y = 0, $z = 0, $yaw = 0, $pitch = 0){ if($x instanceof Location){ return new Location($this->x + $x->x, $this->y + $x->y, $this->z + $x->z, $this->yaw + $x->yaw, $this->pitch + $x->pitch, $this->level); }else{ return new Location($this->x + $x, $this->y + $y, $this->z + $z, $this->yaw + $yaw, $this->pitch + $pitch, $this->level); } } /** * @return float */ public function getYaw(){ return $this->yaw; } /** * @return float */ public function getPitch(){ return $this->pitch; } /** * @param Vector3 $pos * @param $x * @param $y * @param $z * * @return $this */ public function fromObjectAdd(Vector3 $pos, $x, $y, $z){ if($pos instanceof Location){ $this->yaw = $pos->yaw; $this->pitch = $pos->pitch; } parent::fromObjectAdd($pos, $x, $y, $z); return $this; } /** * @return string */ public function __toString(){ return "Location (level=" . ($this->isValid() ? $this->getLevel()->getName() : "null") . ", x=$this->x, y=$this->y, z=$this->z, yaw=$this->yaw, pitch=$this->pitch)"; } } typeOfHit = 0; $ob->blockX = $x; $ob->blockY = $y; $ob->blockZ = $z; $ob->hitVector = new Vector3($hitVector->x, $hitVector->y, $hitVector->z); return $ob; } /** * @param Entity $entity * * @return MovingObjectPosition */ public static function fromEntity(Entity $entity){ $ob = new MovingObjectPosition; $ob->typeOfHit = 1; $ob->entityHit = $entity; $ob->hitVector = new Vector3($entity->x, $entity->y, $entity->z); return $ob; } } x = $x; $this->y = $y; $this->z = $z; $this->level = $level; } /** * @param Vector3 $pos * @param Level|null $level * * @return Position */ public static function fromObject(Vector3 $pos, Level $level = null){ return new Position($pos->x, $pos->y, $pos->z, $level); } /** * Return a Position instance * * @return Position */ public function asPosition() : Position{ return new Position($this->x, $this->y, $this->z, $this->level); } /** * @param int|Vector3 $x * @param int $y * @param int $z * * @return Position */ public function add($x, $y = 0, $z = 0){ if($x instanceof Vector3){ return new Position($this->x + $x->x, $this->y + $x->y, $this->z + $x->z, $this->level); }else{ return new Position($this->x + $x, $this->y + $y, $this->z + $z, $this->level); } } /** * @return Level */ public function getLevel(){ if($this->level !== null and $this->level->isClosed()){ MainLogger::getLogger()->debug("Position was holding a reference to an unloaded Level"); $this->level = null; } return $this->level; } /** * Sets the target Level of the position. * * @param Level|null $level * * @return $this * * @throws \InvalidArgumentException if the specified Level has been closed */ public function setLevel(Level $level = null){ if($level !== null and $level->isClosed()){ throw new \InvalidArgumentException("Specified level has been unloaded and cannot be used"); } $this->level = $level; return $this; } /** * Checks if this object has a valid reference to a loaded Level * * @return bool */ public function isValid(){ return $this->getLevel() instanceof Level; } /** * Returns a side Vector * * @param int $side * @param int $step * * @return Position * * @throws LevelException */ public function getSide($side, $step = 1){ if(!$this->isValid()){ throw new LevelException("Undefined Level reference"); } return Position::fromObject(parent::getSide($side, $step), $this->level); } /** * @return string */ public function __toString(){ return "Position(level=" . ($this->isValid() ? $this->getLevel()->getName() : "null") . ",x=" . $this->x . ",y=" . $this->y . ",z=" . $this->z . ")"; } /** * @param $x * @param $y * @param $z * * @return Position */ public function setComponents($x, $y, $z){ $this->x = $x; $this->y = $y; $this->z = $z; return $this; } /** * @param Vector3 $pos * @param $x * @param $y * @param $z * * @return $this */ public function fromObjectAdd(Vector3 $pos, $x, $y, $z){ if($pos instanceof Position){ $this->level = $pos->level; } parent::fromObjectAdd($pos, $x, $y, $z); return $this; } } seed = $seed; $this->waterHeight = $waterHeight; } /** * @return int */ public function getWaterHeight() : int{ return $this->waterHeight; } /** * Gets the raw block id. * * @param int $x * @param int $y * @param int $z * * @return int 0-255 */ public function getBlockIdAt(int $x, int $y, int $z) : int{ if($chunk = $this->getChunk($x >> 4, $z >> 4)){ return $chunk->getBlockId($x & 0xf, $y & Level::Y_MASK, $z & 0xf); } return 0; } /** * Sets the raw block id. * * @param int $x * @param int $y * @param int $z * @param int $id 0-255 */ public function setBlockIdAt(int $x, int $y, int $z, int $id){ if($chunk = $this->getChunk($x >> 4, $z >> 4)){ $chunk->setBlockId($x & 0xf, $y & Level::Y_MASK, $z & 0xf, $id); } } /** * Gets the raw block metadata * * @param int $x * @param int $y * @param int $z * * @return int 0-15 */ public function getBlockDataAt(int $x, int $y, int $z) : int{ if($chunk = $this->getChunk($x >> 4, $z >> 4)){ return $chunk->getBlockData($x & 0xf, $y & Level::Y_MASK, $z & 0xf); } return 0; } /** * Sets the raw block metadata. * * @param int $x * @param int $y * @param int $z * @param int $data 0-15 */ public function setBlockDataAt(int $x, int $y, int $z, int $data){ if($chunk = $this->getChunk($x >> 4, $z >> 4)){ $chunk->setBlockData($x & 0xf, $y & Level::Y_MASK, $z & 0xf, $data); } } /** * Gets the raw block light level * * @param int $x * @param int $y * @param int $z * * @return int 0-15 */ public function getBlockLightAt(int $x, int $y, int $z) : int{ if($chunk = $this->getChunk($x >> 4, $z >> 4)){ return $chunk->getBlockLight($x & 0x0f, $y & 0x7f, $z & 0x0f); } return 0; } /** * Sets the raw block light level. * * @param int $x * @param int $y * @param int $z * @param int $level 0-15 */ public function setBlockLightAt(int $x, int $y, int $z, int $level){ if($chunk = $this->getChunk($x >> 4, $z >> 4)){ $chunk->setBlockLight($x & 0x0f, $y & 0x7f, $z & 0x0f, $level & 0x0f); } } /** * Updates the light around the block * * @param $x * @param $y * @param $z */ public function updateBlockLight(int $x, int $y, int $z){ $lightPropagationQueue = new \SplQueue(); $lightRemovalQueue = new \SplQueue(); $visited = []; $removalVisited = []; $oldLevel = $this->getBlockLightAt($x, $y, $z); $newLevel = (int) Block::$light[$this->getBlockIdAt($x, $y, $z)]; if($oldLevel !== $newLevel){ $this->setBlockLightAt($x, $y, $z, $newLevel); if($newLevel < $oldLevel){ $removalVisited[Level::blockHash($x, $y, $z)] = true; $lightRemovalQueue->enqueue([new Vector3($x, $y, $z), $oldLevel]); }else{ $visited[Level::blockHash($x, $y, $z)] = true; $lightPropagationQueue->enqueue(new Vector3($x, $y, $z)); } } while(!$lightRemovalQueue->isEmpty()){ /** @var Vector3 $node */ $val = $lightRemovalQueue->dequeue(); $node = $val[0]; $lightLevel = $val[1]; $this->computeRemoveBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); $this->computeRemoveBlockLight($node->x + 1, $node->y, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); $this->computeRemoveBlockLight($node->x, $node->y - 1, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); $this->computeRemoveBlockLight($node->x, $node->y + 1, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); $this->computeRemoveBlockLight($node->x, $node->y, $node->z - 1, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); $this->computeRemoveBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); } while(!$lightPropagationQueue->isEmpty()){ /** @var Vector3 $node */ $node = $lightPropagationQueue->dequeue(); $lightLevel = $this->getBlockLightAt($node->x, $node->y, $node->z) - (int) Block::$lightFilter[$this->getBlockIdAt($node->x, $node->y, $node->z)]; if($lightLevel >= 1){ $this->computeSpreadBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited); $this->computeSpreadBlockLight($node->x + 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited); $this->computeSpreadBlockLight($node->x, $node->y - 1, $node->z, $lightLevel, $lightPropagationQueue, $visited); $this->computeSpreadBlockLight($node->x, $node->y + 1, $node->z, $lightLevel, $lightPropagationQueue, $visited); $this->computeSpreadBlockLight($node->x, $node->y, $node->z - 1, $lightLevel, $lightPropagationQueue, $visited); $this->computeSpreadBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightPropagationQueue, $visited); } } } /** * @param $x * @param $y * @param $z * @param $currentLight * @param \SplQueue $queue * @param \SplQueue $spreadQueue * @param array $visited * @param array $spreadVisited */ private function computeRemoveBlockLight($x, $y, $z, $currentLight, \SplQueue $queue, \SplQueue $spreadQueue, array &$visited, array &$spreadVisited){ $current = $this->getBlockLightAt($x, $y, $z); if($current !== 0 and $current < $currentLight){ $this->setBlockLightAt($x, $y, $z, 0); if(!isset($visited[$index = Level::blockHash($x, $y, $z)])){ $visited[$index] = true; if($current > 1){ $queue->enqueue([new Vector3($x, $y, $z), $current]); } } }elseif($current >= $currentLight){ if(!isset($spreadVisited[$index = Level::blockHash($x, $y, $z)])){ $spreadVisited[$index] = true; $spreadQueue->enqueue(new Vector3($x, $y, $z)); } } } /** * @param $x * @param $y * @param $z * @param $currentLight * @param \SplQueue $queue * @param array $visited */ private function computeSpreadBlockLight($x, $y, $z, $currentLight, \SplQueue $queue, array &$visited){ $current = $this->getBlockLightAt($x, $y, $z); if($current < $currentLight){ $this->setBlockLightAt($x, $y, $z, $currentLight); if(!isset($visited[$index = Level::blockHash($x, $y, $z)])){ $visited[$index] = true; if($currentLight > 1){ $queue->enqueue(new Vector3($x, $y, $z)); } } } } /** * @param int $chunkX * @param int $chunkZ * * @return Chunk|null */ public function getChunk(int $chunkX, int $chunkZ){ return isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) ? $this->chunks[$index] : null; } /** * @param int $chunkX * @param int $chunkZ * @param Chunk $chunk */ public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk = null){ if($chunk === null){ unset($this->chunks[Level::chunkHash($chunkX, $chunkZ)]); return; } $this->chunks[Level::chunkHash($chunkX, $chunkZ)] = $chunk; } public function cleanChunks(){ $this->chunks = []; } /** * Gets the level seed * * @return int|string */ public function getSeed(){ return $this->seed; } }level->getBlockSkyLightAt($x, $y, $z); } public function setLight(int $x, int $y, int $z, int $level) { $this->level->setBlockSkyLightAt($x, $y, $z, $level); } }x = $x; $this->y = $y; $this->z = $z; $this->levelId = ($level !== null ? $level->getId() : -1); } /** * @param Vector3 $pos * @param Level|null $level * * @return WeakPosition */ public static function fromObject(Vector3 $pos, Level $level = null){ return new WeakPosition($pos->x, $pos->y, $pos->z, $level); } /** * @return Level|null */ public function getLevel(){ return Server::getInstance()->getLevel($this->levelId); } /** * @param Level|null $level * * @return $this * * @throws \InvalidArgumentException if the specified Level has been closed */ public function setLevel(Level $level = null){ if($level !== null and $level->isClosed()){ throw new \InvalidArgumentException("Specified level has been unloaded and cannot be used"); } $this->levelId = ($level !== null ? $level->getId() : -1); return $this; } /** * Returns a side Vector * * @param int $side * @param int $step * * @return WeakPosition * * @throws LevelException */ public function getSide($side, $step = 1){ assert($this->isValid()); return WeakPosition::fromObject(parent::getSide($side, $step), $this->level); } /** * @return string */ public function __toString(){ return "Weak" . parent::__toString(); } }x = $chunkX; $this->z = $chunkZ; $this->height = Chunk::MAX_SUBCHUNKS; //TODO: add a way of changing this $this->emptySubChunk = new EmptySubChunk(); foreach($subChunks as $y => $subChunk){ if($y < 0 or $y >= $this->height){ throw new ChunkException("Invalid subchunk index $y!"); } if($subChunk->isEmpty()){ $this->subChunks[$y] = $this->emptySubChunk; }else{ $this->subChunks[$y] = $subChunk; } } for($i = 0; $i < $this->height; ++$i){ if(!isset($this->subChunks[$i])){ $this->subChunks[$i] = $this->emptySubChunk; } } if(count($heightMap) === 256){ $this->heightMap = $heightMap; }else{ assert(count($heightMap) === 0, "Wrong HeightMap value count, expected 256, got " . count($heightMap)); $val = ($this->height * 16); $this->heightMap = array_fill(0, 256, $val); } if(strlen($biomeIds) === 256){ $this->biomeIds = $biomeIds; }else{ assert(strlen($biomeIds) === 0, "Wrong BiomeIds value count, expected 256, got " . strlen($biomeIds)); $this->biomeIds = str_repeat("\x00", 256); } $this->NBTtiles = $tiles; $this->NBTentities = $entities; } /** * @return int */ public function getX() : int{ return $this->x; } /** * @return int */ public function getZ() : int{ return $this->z; } public function setX(int $x){ $this->x = $x; } /** * @param int $z */ public function setZ(int $z){ $this->z = $z; } /** * Returns the chunk height in count of subchunks. * * @return int */ public function getHeight() : int{ return $this->height; } /** * Returns a bitmap of block ID and meta at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * * @return int bitmap, (id << 4) | meta */ public function getFullBlock(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getFullBlock($x, $y & 0x0f, $z); } /** * Sets block ID and meta in one call at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * @param int|null $blockId 0-255 if null, does not change * @param int|null $meta 0-15 if null, does not change * * @return bool */ public function setBlock(int $x, int $y, int $z, $blockId = null, $meta = null) : bool{ if($this->getSubChunk($y >> 4, true)->setBlock($x, $y & 0x0f, $z, $blockId !== null ? ($blockId & 0xff) : null, $meta !== null ? ($meta & 0x0f) : null)){ $this->hasChanged = true; return true; } return false; } /** * Returns the block ID at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * * @return int 0-255 */ public function getBlockId(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockId($x, $y & 0x0f, $z); } /** * Sets the block ID at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * @param int $id 0-255 */ public function setBlockId(int $x, int $y, int $z, int $id){ if($this->getSubChunk($y >> 4, true)->setBlockId($x, $y & 0x0f, $z, $id)){ $this->hasChanged = true; } } /** * Returns the block meta value at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * * @return int 0-15 */ public function getBlockData(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockData($x, $y & 0x0f, $z); } /** * Sets the block meta value at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * @param int $data 0-15 */ public function setBlockData(int $x, int $y, int $z, int $data){ if($this->getSubChunk($y >> 4)->setBlockData($x, $y & 0x0f, $z, $data)){ $this->hasChanged = true; } } /** * Returns the raw block extra data value at the specified chunk block coordinates, or 0 if no data exists * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * * @return int bitmap, (meta << 8) | id */ public function getBlockExtraData(int $x, int $y, int $z) : int{ return $this->extraData[Chunk::chunkBlockHash($x, $y, $z)] ?? 0; } /** * Sets the raw block extra data value at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * @param int $data bitmap, (meta << 8) | id */ public function setBlockExtraData(int $x, int $y, int $z, int $data){ if($data === 0){ unset($this->extraData[Chunk::chunkBlockHash($x, $y, $z)]); }else{ $this->extraData[Chunk::chunkBlockHash($x, $y, $z)] = $data; } $this->hasChanged = true; } /** * Returns the sky light level at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * * @return int 0-15 */ public function getBlockSkyLight(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockSkyLight($x, $y & 0x0f, $z); } /** * Sets the sky light level at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * @param int $level 0-15 */ public function setBlockSkyLight(int $x, int $y, int $z, int $level){ if($this->getSubChunk($y >> 4)->setBlockSkyLight($x, $y & 0x0f, $z, $level)){ $this->hasChanged = true; } } /** * Returns the block light level at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y 0-15 * @param int $z 0-15 * * @return int 0-15 */ public function getBlockLight(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockLight($x, $y & 0x0f, $z); } /** * Sets the block light level at the specified chunk block coordinates * * @param int $x 0-15 * @param int $y 0-15 * @param int $z 0-15 * @param int $level 0-15 */ public function setBlockLight(int $x, int $y, int $z, int $level){ if($this->getSubChunk($y >> 4)->setBlockLight($x, $y & 0x0f, $z, $level)){ $this->hasChanged = true; } } /** * Returns the Y coordinate of the highest non-air block at the specified X/Z chunk block coordinates * * @param int $x 0-15 * @param int $z 0-15 * * @return int 0-255, or -1 if there are no blocks in the column */ public function getHighestBlockAt(int $x, int $z) : int{ $index = $this->getHighestSubChunkIndex(); if($index === -1){ return -1; } $height = $index << 4; for($y = $index; $y >= 0; --$y){ $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z) | ($y << 4); if($height !== -1){ return $height; } } return -1; } /** * Returns the heightmap value at the specified X/Z chunk block coordinates * * @param int $x 0-15 * @param int $z 0-15 * * @return int */ public function getHeightMap(int $x, int $z) : int{ return $this->heightMap[($z << 4) | $x]; } /** * Returns the heightmap value at the specified X/Z chunk block coordinates * @param int $x 0-15 * @param int $z 0-15 * @param int $value */ public function setHeightMap(int $x, int $z, int $value){ $this->heightMap[($z << 4) | $x] = $value; } /** * Recalculates the heightmap for the whole chunk. */ public function recalculateHeightMap(){ for($z = 0; $z < 16; ++$z){ for($x = 0; $x < 16; ++$x){ $this->recalculateHeightMapColumn($x, $z); } } } /** * Recalculates the heightmap for the block column at the specified X/Z chunk coordinates * * @param int $x 0-15 * @param int $z 0-15 * * @return int New calculated heightmap value (0-256 inclusive) */ public function recalculateHeightMapColumn(int $x, int $z) : int{ $max = $this->getHighestBlockAt($x, $z); for($y = $max; $y >= 0; --$y){ if(Block::$lightFilter[$id = $this->getBlockId($x, $y, $z)] > 1 or Block::$diffusesSkyLight[$id]){ break; } } $this->setHeightMap($x, $z, $y + 1); return $y + 1; } /** * Performs basic sky light population on the chunk. * This does not cater for adjacent sky light, this performs direct sky light population only. This may cause some strange visual artifacts * if the chunk is light-populated after being terrain-populated. * * TODO: fast adjacent light spread */ public function populateSkyLight(){ for($x = 0; $x < 16; ++$x){ for($z = 0; $z < 16; ++$z){ $heightMap = $this->getHeightMap($x, $z); $y = ($this->getHighestSubChunkIndex() + 1) << 4; for(; $y >= $heightMap; --$y){ $this->setBlockSkyLight($x, $y, $z, 15); } $light = 15; for(; $y > 0; --$y){ if($light > 0){ $light -= Block::$lightFilter[$this->getBlockId($x, $y, $z)]; if($light < 0){ $light = 0; } } $this->setBlockSkyLight($x, $y, $z, $light); } } } } /** * Returns the biome ID at the specified X/Z chunk block coordinates * * @param int $x 0-15 * @param int $z 0-15 * * @return int 0-255 */ public function getBiomeId(int $x, int $z) : int{ return ord($this->biomeIds{($z << 4) | $x}); } /** * Sets the biome ID at the specified X/Z chunk block coordinates * * @param int $x 0-15 * @param int $z 0-15 * @param int $biomeId 0-255 */ public function setBiomeId(int $x, int $z, int $biomeId){ $this->hasChanged = true; $this->biomeIds{($z << 4) | $x} = chr($biomeId & 0xff); } /** * Returns a column of block IDs from bottom to top at the specified X/Z chunk block coordinates. * @param int $x 0-15 * @param int $z 0-15 * * @return string */ public function getBlockIdColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ $result .= $subChunk->getBlockIdColumn($x, $z); } return $result; } /** * Returns a column of block meta values from bottom to top at the specified X/Z chunk block coordinates. * @param int $x 0-15 * @param int $z 0-15 * * @return string */ public function getBlockDataColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ $result .= $subChunk->getBlockDataColumn($x, $z); } return $result; } /** * Returns a column of sky light values from bottom to top at the specified X/Z chunk block coordinates. * @param int $x 0-15 * @param int $z 0-15 * * @return string */ public function getBlockSkyLightColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ $result .= $subChunk->getSkyLightColumn($x, $z); } return $result; } /** * Returns a column of block light values from bottom to top at the specified X/Z chunk block coordinates. * @param int $x 0-15 * @param int $z 0-15 * * @return string */ public function getBlockLightColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ $result .= $subChunk->getBlockLightColumn($x, $z); } return $result; } /** * @return bool */ public function isLightPopulated() : bool{ return $this->lightPopulated; } /** * @param bool $value */ public function setLightPopulated(bool $value = true){ $this->lightPopulated = $value; } /** * @return bool */ public function isPopulated() : bool{ return $this->terrainPopulated; } /** * @param bool $value */ public function setPopulated(bool $value = true){ $this->terrainPopulated = $value; } /** * @return bool */ public function isGenerated() : bool{ return $this->terrainGenerated; } /** * @param bool $value */ public function setGenerated(bool $value = true){ $this->terrainGenerated = $value; } /** * @param Entity $entity */ public function addEntity(Entity $entity){ $this->entities[$entity->getId()] = $entity; if(!($entity instanceof Player) and $this->isInit){ $this->hasChanged = true; } } /** * @param Entity $entity */ public function removeEntity(Entity $entity){ unset($this->entities[$entity->getId()]); if(!($entity instanceof Player) and $this->isInit){ $this->hasChanged = true; } } /** * @param Tile $tile */ public function addTile(Tile $tile){ $this->tiles[$tile->getId()] = $tile; if(isset($this->tileList[$index = (($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]) and $this->tileList[$index] !== $tile){ $this->tileList[$index]->close(); } $this->tileList[$index] = $tile; if($this->isInit){ $this->hasChanged = true; } } /** * @param Tile $tile */ public function removeTile(Tile $tile){ unset($this->tiles[$tile->getId()]); unset($this->tileList[(($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]); if($this->isInit){ $this->hasChanged = true; } } /** * Returns an array of entities currently using this chunk. * * @return Entity[] */ public function getEntities() : array{ return $this->entities; } /** * @return Tile[] */ public function getTiles() : array{ return $this->tiles; } /** * Returns the tile at the specified chunk block coordinates, or null if no tile exists. * * @param int $x 0-15 * @param int $y * @param int $z 0-15 * * @return Tile|null */ public function getTile(int $x, int $y, int $z){ $index = ($x << 12) | ($z << 8) | $y; return $this->tileList[$index] ?? null; } /** * Unloads the chunk, closing entities and tiles. * * @param bool $safe Whether to check if there are still players using this chunk * * @return bool */ public function unload(bool $safe = true) : bool{ if($safe){ foreach($this->getEntities() as $entity){ if($entity instanceof Player){ return false; } } } foreach($this->getEntities() as $entity){ if($entity instanceof Player){ continue; } $entity->close(); } foreach($this->getTiles() as $tile){ $tile->close(); } return true; } /** * Deserializes tiles and entities from NBT * * @param Level $level */ public function initChunk(Level $level){ if(!$this->isInit){ $changed = false; if($this->NBTentities !== null){ $level->timings->syncChunkLoadEntitiesTimer->startTiming(); foreach($this->NBTentities as $nbt){ if($nbt instanceof CompoundTag){ if(!isset($nbt->id)){ $changed = true; continue; } if(($nbt["Pos"][0] >> 4) !== $this->x or ($nbt["Pos"][2] >> 4) !== $this->z){ $changed = true; continue; //Fixes entities allocated in wrong chunks. } if(!(($entity = Entity::createEntity($nbt["id"], $level, $nbt)) instanceof Entity)){ $changed = true; continue; } } } $level->timings->syncChunkLoadEntitiesTimer->stopTiming(); $level->timings->syncChunkLoadTileEntitiesTimer->startTiming(); foreach($this->NBTtiles as $nbt){ if($nbt instanceof CompoundTag){ if(!isset($nbt->id)){ $changed = true; continue; } if(($nbt["x"] >> 4) !== $this->x or ($nbt["z"] >> 4) !== $this->z){ $changed = true; continue; //Fixes tiles allocated in wrong chunks. } if(Tile::createTile($nbt["id"], $level, $nbt) === null){ $changed = true; continue; } } } $level->timings->syncChunkLoadTileEntitiesTimer->stopTiming(); $this->NBTentities = null; $this->NBTtiles = null; } $this->hasChanged = $changed; $this->isInit = true; } } /** * @return string */ public function getBiomeIdArray() : string{ return $this->biomeIds; } /** * @return int[] */ public function getHeightMapArray() : array{ return $this->heightMap; } /** * @return int[] */ public function getBlockExtraDataArray() : array{ return $this->extraData; } /** * @return bool */ public function hasChanged() : bool{ return $this->hasChanged; } /** * @param bool $value */ public function setChanged(bool $value = true){ $this->hasChanged = $value; } /** * Returns the subchunk at the specified subchunk Y coordinate, or an empty, unmodifiable stub if it does not exist or the coordinate is out of range. * * @param int $y * @param bool $generateNew Whether to create a new, modifiable subchunk if there is not one in place * * @return SubChunk|EmptySubChunk */ public function getSubChunk(int $y, bool $generateNew = false) : SubChunk{ if($y < 0 or $y >= $this->height){ return $this->emptySubChunk; }elseif($generateNew and $this->subChunks[$y] instanceof EmptySubChunk){ $this->subChunks[$y] = new SubChunk(); } assert($this->subChunks[$y] !== null, "Somehow something broke, no such subchunk at index $y"); return $this->subChunks[$y]; } /** * Sets a subchunk in the chunk index * @param int $y * @param SubChunk|null $subChunk * @param bool $allowEmpty Whether to check if the chunk is empty, and if so replace it with an empty stub * * @return bool */ public function setSubChunk(int $y, SubChunk $subChunk = null, bool $allowEmpty = false) : bool{ if($y < 0 or $y >= $this->height){ return false; } if($subChunk === null or ($subChunk->isEmpty() and !$allowEmpty)){ $this->subChunks[$y] = $this->emptySubChunk; }else{ $this->subChunks[$y] = $subChunk; } $this->hasChanged = true; return true; } /** * @return SubChunk[] */ public function getSubChunks() : array{ return $this->subChunks; } /** * Returns the Y coordinate of the highest non-empty subchunk in this chunk. * * @return int */ public function getHighestSubChunkIndex() : int{ for($y = count($this->subChunks) - 1; $y >= 0; --$y){ if($this->subChunks[$y] === null or $this->subChunks[$y] instanceof EmptySubChunk){ //No need to thoroughly prune empties at runtime, this will just reduce performance. continue; } break; } return $y; } /** * Returns the count of subchunks that need sending to players * * @return int */ public function getSubChunkSendCount() : int{ return $this->getHighestSubChunkIndex() + 1; } /** * Disposes of empty subchunks */ public function pruneEmptySubChunks(){ foreach($this->subChunks as $y => $subChunk){ if($y < 0 or $y >= $this->height){ assert(false, "Invalid subchunk index"); unset($this->subChunks[$y]); }elseif($subChunk instanceof EmptySubChunk){ continue; }elseif($subChunk->isEmpty()){ //normal subchunk full of air, remove it and replace it with an empty stub $this->subChunks[$y] = $this->emptySubChunk; }else{ continue; //do not set changed } $this->hasChanged = true; } } /** * Serializes the chunk for sending to players * * @return string */ public function networkSerialize() : string{ $result = ""; $subChunkCount = $this->getSubChunkSendCount(); $result .= chr($subChunkCount); for($y = 0; $y < $subChunkCount; ++$y){ $result .= $this->subChunks[$y]->networkSerialize(); } $result .= pack("v*", ...$this->heightMap) . $this->biomeIds . chr(0); //border block array count //Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client. $extraData = new BinaryStream(); $extraData->putVarInt(count($this->extraData)); //WHY, Mojang, WHY foreach($this->extraData as $key => $value){ $extraData->putVarInt($key); $extraData->putLShort($value); } $result .= $extraData->getBuffer(); if(count($this->tiles) > 0){ $nbt = new NBT(NBT::LITTLE_ENDIAN); $list = []; foreach($this->tiles as $tile){ if($tile instanceof Spawnable){ $list[] = $tile->getSpawnCompound(); } } $nbt->setData($list); $result .= $nbt->write(true); } return $result; } /** * Fast-serializes the chunk for passing between threads * TODO: tiles and entities * * @return string */ public function fastSerialize() : string{ $stream = new BinaryStream(); $stream->putInt($this->x); $stream->putInt($this->z); $count = 0; $subChunks = ""; foreach($this->subChunks as $y => $subChunk){ if($subChunk instanceof EmptySubChunk){ continue; } ++$count; $subChunks .= chr($y) . $subChunk->fastSerialize(); } $stream->putByte($count); $stream->put($subChunks); $stream->put(pack("v*", ...$this->heightMap) . $this->biomeIds . chr(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0))); return $stream->getBuffer(); } /** * Deserializes a fast-serialized chunk * * @param string $data * * @return Chunk */ public static function fastDeserialize(string $data){ $stream = new BinaryStream(); $stream->setBuffer($data); $data = null; $x = $stream->getInt(); $z = $stream->getInt(); $subChunks = []; $count = $stream->getByte(); for($y = 0; $y < $count; ++$y){ $subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240)); } $heightMap = array_values(unpack("v*", $stream->get(512))); $biomeIds = $stream->get(256); $chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap); $flags = $stream->getByte(); $chunk->lightPopulated = (bool) ($flags & 4); $chunk->terrainPopulated = (bool) ($flags & 2); $chunk->terrainGenerated = (bool) ($flags & 1); return $chunk; } //TODO: get rid of this public static function getEmptyChunk(int $x, int $z) : Chunk{ return new Chunk($x, $z); } /** * Creates a block hash from chunk block coordinates. Used for extra data keys in chunk packets. * @internal * * @param int $x 0-15 * @param int $y 0-255 * @param int $z 0-15 * * @return int */ public static function chunkBlockHash(int $x, int $y, int $z) : int{ return ($x << 12) | ($z << 8) | $y; } }ids, $ids, 4096); self::assignData($this->data, $data, 2048); self::assignData($this->skyLight, $skyLight, 2048, "\xff"); self::assignData($this->blockLight, $blockLight, 2048); } /** * @return bool */ public function isEmpty() : bool{ assert(strlen($this->ids) === 4096, "Wrong length of ID array, expecting 4096 bytes, got " . strlen($this->ids)); return substr_count($this->ids, "\x00") === 4096; } /** * @param int $x * @param int $y * @param int $z * * @return int */ public function getBlockId(int $x, int $y, int $z) : int{ return ord($this->ids{($x << 8) | ($z << 4) | $y}); } /** * @param int $x * @param int $y * @param int $z * @param int $id * * @return bool */ public function setBlockId(int $x, int $y, int $z, int $id) : bool{ $this->ids{($x << 8) | ($z << 4) | $y} = chr($id); return true; } /** * @param int $x * @param int $y * @param int $z * * @return int */ public function getBlockData(int $x, int $y, int $z) : int{ $m = ord($this->data{($x << 7) + ($z << 3) + ($y >> 1)}); if(($y & 1) === 0){ return $m & 0x0f; }else{ return $m >> 4; } } /** * @param int $x * @param int $y * @param int $z * @param int $data * * @return bool */ public function setBlockData(int $x, int $y, int $z, int $data) : bool{ $i = ($x << 7) | ($z << 3) | ($y >> 1); if(($y & 1) === 0){ $this->data{$i} = chr((ord($this->data{$i}) & 0xf0) | ($data & 0x0f)); }else{ $this->data{$i} = chr((($data & 0x0f) << 4) | (ord($this->data{$i}) & 0x0f)); } return true; } /** * @param int $x * @param int $y * @param int $z * * @return int */ public function getFullBlock(int $x, int $y, int $z) : int{ $i = ($x << 8) | ($z << 4) | $y; if(($y & 1) === 0){ return (ord($this->ids{$i}) << 4) | (ord($this->data{$i >> 1}) & 0x0f); }else{ return (ord($this->ids{$i}) << 4) | (ord($this->data{$i >> 1}) >> 4); } } /** * @param int $x * @param int $y * @param int $z * @param null $id * @param null $data * * @return bool */ public function setBlock(int $x, int $y, int $z, $id = null, $data = null) : bool{ $i = ($x << 8) | ($z << 4) | $y; $changed = false; if($id !== null){ $block = chr($id); if($this->ids{$i} !== $block){ $this->ids{$i} = $block; $changed = true; } } if($data !== null){ $i >>= 1; $byte = ord($this->data{$i}); if(($y & 1) === 0){ $this->data{$i} = chr(($byte & 0xf0) | ($data & 0x0f)); }else{ $this->data{$i} = chr((($data & 0x0f) << 4) | ($byte & 0x0f)); } if($this->data{$i} !== $byte){ $changed = true; } } return $changed; } /** * @param int $x * @param int $y * @param int $z * * @return int */ public function getBlockLight(int $x, int $y, int $z) : int{ $byte = ord($this->blockLight{($x << 7) + ($z << 3) + ($y >> 1)}); if(($y & 1) === 0){ return $byte & 0x0f; }else{ return $byte >> 4; } } /** * @param int $x * @param int $y * @param int $z * @param int $level * * @return bool */ public function setBlockLight(int $x, int $y, int $z, int $level) : bool{ $i = ($x << 7) + ($z << 3) + ($y >> 1); $byte = ord($this->blockLight{$i}); if(($y & 1) === 0){ $this->blockLight{$i} = chr(($byte & 0xf0) | ($level & 0x0f)); }else{ $this->blockLight{$i} = chr((($level & 0x0f) << 4) | ($byte & 0x0f)); } return true; } /** * @param int $x * @param int $y * @param int $z * * @return int */ public function getBlockSkyLight(int $x, int $y, int $z) : int{ $byte = ord($this->skyLight{($x << 7) + ($z << 3) + ($y >> 1)}); if(($y & 1) === 0){ return $byte & 0x0f; }else{ return $byte >> 4; } } /** * @param int $x * @param int $y * @param int $z * @param int $level * * @return bool */ public function setBlockSkyLight(int $x, int $y, int $z, int $level) : bool{ $i = ($x << 7) + ($z << 3) + ($y >> 1); $byte = ord($this->skyLight{$i}); if(($y & 1) === 0){ $this->skyLight{$i} = chr(($byte & 0xf0) | ($level & 0x0f)); }else{ $this->skyLight{$i} = chr((($level & 0x0f) << 4) | ($byte & 0x0f)); } return true; } /** * @param int $x * @param int $z * * @return int */ public function getHighestBlockAt(int $x, int $z) : int{ for($y = 15; $y >= 0; --$y){ if($this->ids{($x << 8) | ($z << 4) | $y} !== "\x00"){ return $y; } } return -1; //highest block not in this subchunk } /** * @param int $x * @param int $z * * @return string */ public function getBlockIdColumn(int $x, int $z) : string{ return substr($this->ids, (($x << 8) | ($z << 4)), 16); } /** * @param int $x * @param int $z * * @return string */ public function getBlockDataColumn(int $x, int $z) : string{ return substr($this->data, (($x << 7) | ($z << 3)), 8); } /** * @param int $x * @param int $z * * @return string */ public function getBlockLightColumn(int $x, int $z) : string{ return substr($this->blockLight, (($x << 7) | ($z << 3)), 8); } /** * @param int $x * @param int $z * * @return string */ public function getSkyLightColumn(int $x, int $z) : string{ return substr($this->skyLight, (($x << 7) | ($z << 3)), 8); } /** * @return string */ public function getBlockIdArray() : string{ assert(strlen($this->ids) === 4096, "Wrong length of ID array, expecting 4096 bytes, got " . strlen($this->ids)); return $this->ids; } /** * @return string */ public function getBlockDataArray() : string{ assert(strlen($this->data) === 2048, "Wrong length of data array, expecting 2048 bytes, got " . strlen($this->data)); return $this->data; } /** * @return string */ public function getSkyLightArray() : string{ assert(strlen($this->skyLight) === 2048, "Wrong length of skylight array, expecting 2048 bytes, got " . strlen($this->skyLight)); return $this->skyLight; } /** * @return string */ public function getBlockLightArray() : string{ assert(strlen($this->blockLight) === 2048, "Wrong length of light array, expecting 2048 bytes, got " . strlen($this->blockLight)); return $this->blockLight; } /** * @return string */ public function networkSerialize() : string{ // storage version, ids, data, skylight, blocklight return "\x00" . $this->ids . $this->data . $this->skyLight . $this->blockLight; } /** * @return string */ public function fastSerialize() : string{ return $this->ids . $this->data . $this->skyLight . $this->blockLight; } /** * @param string $data * * @return SubChunk */ public static function fastDeserialize(string $data) : SubChunk{ return new SubChunk( substr($data, 0, 4096), //ids substr($data, 4096, 2048), //data substr($data, 6144, 2048), //sky light substr($data, 8192, 2048) //block light ); } }level = $level; $this->path = $path; if(!file_exists($this->path)){ mkdir($this->path, 0777, true); } $nbt = new NBT(NBT::BIG_ENDIAN); $nbt->readCompressed(file_get_contents($this->getPath() . "level.dat")); $levelData = $nbt->getData(); if($levelData->Data instanceof CompoundTag){ $this->levelData = $levelData->Data; }else{ throw new LevelException("Invalid level.dat"); } if(!isset($this->levelData->generatorName)){ $this->levelData->generatorName = new StringTag("generatorName", Generator::getGenerator("DEFAULT")); } if(!isset($this->levelData->generatorOptions)){ $this->levelData->generatorOptions = new StringTag("generatorOptions", ""); } $this->asyncChunkRequest = (bool) $this->level->getServer()->getProperty("chunk-sending.async-chunk-request", false); } /** * @return string */ public function getPath() : string{ return $this->path; } /** * @return \pocketmine\Server */ public function getServer(){ return $this->level->getServer(); } /** * @return Level */ public function getLevel(){ return $this->level; } /** * @return string */ public function getName() : string{ return (string) $this->levelData["LevelName"]; } /** * @return mixed|null */ public function getTime(){ return $this->levelData["Time"]; } /** * @param int|string $value */ public function setTime($value){ $this->levelData->Time = new LongTag("Time", $value); } /** * @return mixed|null */ public function getSeed(){ return $this->levelData["RandomSeed"]; } /** * @param int|string $value */ public function setSeed($value){ $this->levelData->RandomSeed = new LongTag("RandomSeed", (int) $value); } /** * @return Vector3 */ public function getSpawn() : Vector3{ return new Vector3((float) $this->levelData["SpawnX"], (float) $this->levelData["SpawnY"], (float) $this->levelData["SpawnZ"]); } /** * @param Vector3 $pos */ public function setSpawn(Vector3 $pos){ $this->levelData->SpawnX = new IntTag("SpawnX", (int) $pos->x); $this->levelData->SpawnY = new IntTag("SpawnY", (int) $pos->y); $this->levelData->SpawnZ = new IntTag("SpawnZ", (int) $pos->z); } public function doGarbageCollection(){ } /** * @return CompoundTag */ public function getLevelData() : CompoundTag{ return $this->levelData; } public function saveLevelData(){ $nbt = new NBT(NBT::BIG_ENDIAN); $nbt->setData(new CompoundTag("", [ "Data" => $this->levelData ])); $buffer = $nbt->writeCompressed(); file_put_contents($this->getPath() . "level.dat", $buffer); } /** * @param int $x * @param int $z * * @return null|ChunkRequestTask */ public function requestChunkTask(int $x, int $z){ $chunk = $this->getChunk($x, $z, false); if(!($chunk instanceof Chunk)){ throw new ChunkException("Invalid Chunk sent"); } if($this->asyncChunkRequest){ return new ChunkRequestTask($this->level, $chunk); } //non-async, call the callback directly with serialized data $this->getLevel()->chunkRequestCallback($x, $z, $chunk->networkSerialize()); return null; } } levelId = $level->getId(); $this->chunk = $chunk->fastSerialize(); $this->chunkX = $chunk->getX(); $this->chunkZ = $chunk->getZ(); //TODO: serialize tiles with chunks $tiles = ""; $nbt = new NBT(NBT::LITTLE_ENDIAN); foreach($chunk->getTiles() as $tile){ if($tile instanceof Spawnable){ $nbt->setData($tile->getSpawnCompound()); $tiles .= $nbt->write(true); } } $this->tiles = $tiles; } public function onRun(){ $chunk = Chunk::fastDeserialize($this->chunk); $ordered = $chunk->networkSerialize() . $this->tiles; $this->setResult($ordered, false); } /** * @param Server $server */ public function onCompletion(Server $server){ $level = $server->getLevel($this->levelId); if($level instanceof Level and $this->hasResult()){ $level->chunkRequestCallback($this->chunkX, $this->chunkZ, $this->getResult()); } } } XZY and vice versa) * * @param string $array length 4096 * * @return string length 4096 */ public static final function reorderByteArray(string $array) : string{ $result = str_repeat("\x00", 4096); if($array !== $result){ $i = 0; for($x = 0; $x < 16; ++$x){ $zM = $x + 256; for($z = $x; $z < $zM; $z += 16){ $yM = $z + 4096; for($y = $z; $y < $yM; $y += 256){ $result{$i} = $array{$y}; ++$i; } } } } return $result; } /** * Re-orders a nibble array (YZX -> XZY and vice versa) * * @param string $array length 2048 * @param string $commonValue length 1 common value to fill the default array with and to expect, may improve sort time * * @return string length 2048 */ public static final function reorderNibbleArray(string $array, string $commonValue = "\x00") : string{ $result = str_repeat($commonValue, 2048); if($array !== $result){ $i = 0; for($x = 0; $x < 8; ++$x){ for($z = 0; $z < 16; ++$z){ $zx = (($z << 3) | $x); for($y = 0; $y < 8; ++$y){ $j = (($y << 8) | $zx); $j80 = ($j | 0x80); if($array{$j} === $commonValue and $array{$j80} === $commonValue){ //values are already filled }else{ $i1 = ord($array{$j}); $i2 = ord($array{$j80}); $result{$i} = chr(($i2 << 4) | ($i1 & 0x0f)); $result{$i | 0x80} = chr(($i1 >> 4) | ($i2 & 0xf0)); } $i++; } } $i += 128; } } return $result; } /** * Converts pre-MCPE-1.0 biome color array to biome ID array. * * @param int[] $array of biome color values * * @return string */ public static function convertBiomeColors(array $array) : string{ $result = str_repeat("\x00", 256); foreach($array as $i => $color){ $result{$i} = chr(($color >> 24) & 0xff); } return $result; } }level = $level; $this->path = $path; if(!file_exists($this->path)){ mkdir($this->path, 0777, true); } $nbt = new NBT(NBT::LITTLE_ENDIAN); $nbt->read(substr(file_get_contents($this->getPath() . "level.dat"), 8)); $levelData = $nbt->getData(); if($levelData instanceof CompoundTag){ $this->levelData = $levelData; }else{ throw new LevelException("Invalid level.dat"); } $this->db = new \LevelDB($this->path . "/db", [ "compression" => LEVELDB_ZLIB_COMPRESSION ]); if(isset($this->levelData->StorageVersion) and $this->levelData->StorageVersion->getValue() > self::CURRENT_STORAGE_VERSION){ throw new LevelException("Specified LevelDB world format version is newer than the version supported by the server"); } if(!isset($this->levelData->generatorName)){ if(isset($this->levelData->Generator)){ switch((int) $this->levelData->Generator->getValue()){ //Detect correct generator from MCPE data case self::GENERATOR_FLAT: $this->levelData->generatorName = new StringTag("generatorName", Generator::getGenerator("FLAT")); if(($layers = $this->db->get(self::ENTRY_FLAT_WORLD_LAYERS)) !== false){ //Detect existing custom flat layers $layers = trim($layers, "[]"); }else{ $layers = "7,3,3,2"; } $this->levelData->generatorOptions = new StringTag("generatorOptions", "2;" . $layers . ";1"); break; case self::GENERATOR_INFINITE: //TODO: add a null generator which does not generate missing chunks (to allow importing back to MCPE and generating more normal terrain without PocketMine messing things up) $this->levelData->generatorName = new StringTag("generatorName", Generator::getGenerator("DEFAULT")); $this->levelData->generatorOptions = new StringTag("generatorOptions", ""); break; case self::GENERATOR_LIMITED: throw new LevelException("Limited worlds are not currently supported"); default: throw new LevelException("Unknown LevelDB world format type, this level cannot be loaded"); } }else{ $this->levelData->generatorName = new StringTag("generatorName", Generator::getGenerator("DEFAULT")); } } if(!isset($this->levelData->generatorOptions)){ $this->levelData->generatorOptions = new StringTag("generatorOptions", ""); } } /** * @return string */ public static function getProviderName() : string{ return "leveldb"; } /** * @return int */ public function getWorldHeight() : int{ return 256; } /** * @param string $path * * @return bool */ public static function isValid(string $path) : bool{ return file_exists($path . "/level.dat") and is_dir($path . "/db/"); } /** * @param string $path * @param string $name * @param int|string $seed * @param string $generator * @param array $options */ public static function generate(string $path, string $name, $seed, string $generator, array $options = []){ if(!file_exists($path)){ mkdir($path, 0777, true); } if(!file_exists($path . "/db")){ mkdir($path . "/db", 0777, true); } switch($generator){ case Flat::class: $generatorType = self::GENERATOR_FLAT; break; default: $generatorType = self::GENERATOR_INFINITE; //TODO: add support for limited worlds } $levelData = new CompoundTag("", [ //Vanilla fields "DayCycleStopTime" => new IntTag("DayCycleStopTime", -1), "Difficulty" => new IntTag("Difficulty", 2), "ForceGameType" => new ByteTag("ForceGameType", 0), "GameType" => new IntTag("GameType", 0), "Generator" => new IntTag("Generator", $generatorType), "LastPlayed" => new LongTag("LastPlayed", time()), "LevelName" => new StringTag("LevelName", $name), "NetworkVersion" => new IntTag("NetworkVersion", ProtocolInfo::CURRENT_PROTOCOL), //"Platform" => new IntTag("Platform", 2), //TODO: find out what the possible values are for "RandomSeed" => new LongTag("RandomSeed", $seed), "SpawnX" => new IntTag("SpawnX", 0), "SpawnY" => new IntTag("SpawnY", 32767), "SpawnZ" => new IntTag("SpawnZ", 0), "StorageVersion" => new IntTag("StorageVersion", self::CURRENT_STORAGE_VERSION), "Time" => new LongTag("Time", 0), "eduLevel" => new ByteTag("eduLevel", 0), "falldamage" => new ByteTag("falldamage", 1), "firedamage" => new ByteTag("firedamage", 1), "hasBeenLoadedInCreative" => new ByteTag("hasBeenLoadedInCreative", 1), //badly named, this actually determines whether achievements can be earned in this world... "immutableWorld" => new ByteTag("immutableWorld", 0), "lightningLevel" => new FloatTag("lightningLevel", 0.0), "lightningTime" => new IntTag("lightningTime", 0), "pvp" => new ByteTag("pvp", 1), "rainLevel" => new FloatTag("rainLevel", 0.0), "rainTime" => new IntTag("rainTime", 0), "spawnMobs" => new ByteTag("spawnMobs", 1), "texturePacksRequired" => new ByteTag("texturePacksRequired", 0), //TODO //Additional PocketMine-MP fields "GameRules" => new CompoundTag("GameRules", []), "hardcore" => new ByteTag("hardcore", 0), "generatorName" => new StringTag("generatorName", Generator::getGeneratorName($generator)), "generatorOptions" => new StringTag("generatorOptions", $options["preset"] ?? "") ]); $nbt = new NBT(NBT::LITTLE_ENDIAN); $nbt->setData($levelData); $buffer = $nbt->write(); file_put_contents($path . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); $db = new \LevelDB($path . "/db", [ "compression" => LEVELDB_ZLIB_COMPRESSION ]); if($generatorType === self::GENERATOR_FLAT and isset($options["preset"])){ $layers = explode(";", $options["preset"])[1] ?? ""; if($layers !== ""){ $out = "["; foreach(Flat::parseLayers($layers) as $result){ $out .= $result[0] . ","; //only id, meta will unfortunately not survive :( } $out = rtrim($out, ",") . "]"; //remove trailing comma $db->put(self::ENTRY_FLAT_WORLD_LAYERS, $out); //Add vanilla flatworld layers to allow terrain generation by MCPE to continue seamlessly } } $db->close(); } public function saveLevelData(){ $nbt = new NBT(NBT::LITTLE_ENDIAN); $nbt->setData($this->levelData); $buffer = $nbt->write(); file_put_contents($this->getPath() . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); } public function unloadChunks(){ foreach($this->chunks as $chunk){ $this->unloadChunk($chunk->getX(), $chunk->getZ(), false); } $this->chunks = []; } /** * @return string */ public function getGenerator() : string{ return $this->levelData["generatorName"]; } /** * @return array */ public function getGeneratorOptions() : array{ return ["preset" => $this->levelData["generatorOptions"]]; } /** * @return array */ public function getLoadedChunks() : array{ return $this->chunks; } /** * @param int $x * @param int $z * * @return bool */ public function isChunkLoaded(int $x, int $z) : bool{ return isset($this->chunks[Level::chunkHash($x, $z)]); } public function saveChunks(){ foreach($this->chunks as $chunk){ $this->saveChunk($chunk->getX(), $chunk->getZ()); } } /** * @param int $chunkX * @param int $chunkZ * @param bool $create * * @return bool */ public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : bool{ if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)])){ return true; } $this->level->timings->syncChunkLoadDataTimer->startTiming(); $chunk = $this->readChunk($chunkX, $chunkZ); if($chunk === null and $create){ $chunk = Chunk::getEmptyChunk($chunkX, $chunkZ); } $this->level->timings->syncChunkLoadDataTimer->stopTiming(); if($chunk !== null){ $this->chunks[$index] = $chunk; return true; }else{ return false; } } /** * @param int $chunkX * @param int $chunkZ * * @return Chunk|null */ private function readChunk($chunkX, $chunkZ){ $index = LevelDB::chunkIndex($chunkX, $chunkZ); if(!$this->chunkExists($chunkX, $chunkZ)){ return null; } try{ $subChunks = []; $heightMap = []; $biomeIds = ""; for($y = Chunk::MAX_SUBCHUNKS - 1; $y >= 0; --$y){ if($this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y)) !== false){ //Found subchunk data! break; } } if($y <= 0 and ($legacyTerrain = $this->db->get($index . self::TAG_LEGACY_TERRAIN)) !== false){ //didn't find any subchunk data but found old (pre-1.0) data $offset = 0; $fullIds = substr($legacyTerrain, $offset, 32768); $offset += 32768; $fullData = substr($legacyTerrain, $offset, 16384); $offset += 16384; $fullSkyLight = substr($legacyTerrain, $offset, 16384); $offset += 16384; $fullBlockLight = substr($legacyTerrain, $offset, 16384); $offset += 16384; for($yy = 0; $yy < 8; ++$yy){ $subOffset = ($yy << 4); $ids = ""; for($i = 0; $i < 256; ++$i){ $ids .= substr($fullIds, $subOffset, 16); $subOffset += 128; } $data = ""; $subOffset = ($yy << 3); for($i = 0; $i < 256; ++$i){ $data .= substr($fullData, $subOffset, 8); $subOffset += 64; } $skyLight = ""; $subOffset = ($yy << 3); for($i = 0; $i < 256; ++$i){ $skyLight .= substr($fullSkyLight, $subOffset, 8); $subOffset += 64; } $blockLight = ""; $subOffset = ($yy << 3); for($i = 0; $i < 256; ++$i){ $blockLight .= substr($fullBlockLight, $subOffset, 8); $subOffset += 64; } $subChunks[$yy] = new SubChunk($ids, $data, $skyLight, $blockLight); } $heightMap = array_values(unpack("C*", substr($legacyTerrain, $offset, 256))); $offset += 256; $biomeIds = ChunkUtils::convertBiomeColors(array_values(unpack("N*", substr($legacyTerrain, $offset, 1024)))); $offset += 1024; }else{ for(; $y >= 0; --$y){ //If one subchunk exists, all subchunks below it are also guaranteed to exist. $offset = 1; //Skip subchunk version byte $subChunkData = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y)); $subChunks[$y] = new SubChunk( substr($subChunkData, $offset, 4096), //block ids substr($subChunkData, $offset += 4096, 2048), //block meta substr($subChunkData, $offset += 2048, 2048), //sky light substr($subChunkData, $offset += 2048, 2048) //block light ); } if(($data2dLegacy = $this->db->get($index . self::TAG_DATA_2D_LEGACY)) !== false){ //Found old data, convert it to new format $heightMap = array_values(unpack("C*", substr($data2dLegacy, 0, 256))); $biomeIds = ChunkUtils::convertBiomeColors(array_values(unpack("N*", substr($data2dLegacy, 256, 1024)))); }elseif(($data2d = $this->db->get($index . self::TAG_DATA_2D)) !== false){ $heightMap = array_values(unpack("v*", substr($data2d, 0, 512))); $biomeIds = substr($data2d, 512, 256); } } $nbt = new NBT(NBT::LITTLE_ENDIAN); $entities = []; if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and strlen($entityData) > 0){ $nbt->read($entityData, true); $entities = $nbt->getData(); if(!is_array($entities)){ $entities = [$entities]; } } $tiles = []; if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and strlen($tileData) > 0){ $nbt->read($tileData, true); $tiles = $nbt->getData(); if(!is_array($tiles)){ $tiles = [$tiles]; } } /* $extraData = []; if(($extraRawData = $this->db->get($index . self::TAG_EXTRA_DATA)) !== false){ $stream = new BinaryStream($extraRawData); $count = $stream->getLInt(); //TODO: check if the extra data is BE or LE for($i = 0; $i < $count; ++$i){ $key = $stream->getInt(); $value = $stream->getShort(false); $extraData[$key] = $value; } }*/ //TODO $chunk = new Chunk( $chunkX, $chunkZ, $subChunks, $entities, $tiles, $biomeIds, $heightMap ); //TODO: tile ticks, biome states (?) /* $flags = $this->db->get($index . self::ENTRY_FLAGS); if($flags === false){ $flags = "\x03"; }*/ // TODO: check this, add flags (?) $chunk->setGenerated(true); $chunk->setPopulated(true); $chunk->setLightPopulated(true); return $chunk; }catch(\Throwable $t){ $logger = MainLogger::getLogger(); $logger->error("LevelDB chunk decode error"); $logger->logException($t); return null; } } /** * @param Chunk $chunk */ private function writeChunk(Chunk $chunk){ $index = LevelDB::chunkIndex($chunk->getX(), $chunk->getZ()); $this->db->put($index . self::TAG_VERSION, chr(self::CURRENT_STORAGE_VERSION)); $highestIndex = $chunk->getHighestSubChunkIndex(); $subChunks = $chunk->getSubChunks(); for($y = $highestIndex; $y >= 0; --$y){ //Subchunks behave like a stack $this->db->put($index . self::TAG_SUBCHUNK_PREFIX . chr($y), "\x00" . //Subchunk version byte $subChunks[$y]->getBlockIdArray() . $subChunks[$y]->getBlockDataArray() . $subChunks[$y]->getSkyLightArray() . $subChunks[$y]->getBlockLightArray() ); } $this->db->put($index . self::TAG_DATA_2D, pack("v*", ...$chunk->getHeightMapArray()) . $chunk->getBiomeIdArray()); $this->writeTags($chunk->getTiles(), $index . self::TAG_BLOCK_ENTITY); $this->writeTags($chunk->getEntities(), $index . self::TAG_ENTITY); //TODO: clean up old data } /** * @param array $targets * @param string $index */ private function writeTags(array $targets, string $index){ $nbt = new NBT(NBT::LITTLE_ENDIAN); $out = []; foreach($targets as $target){ if(!$target->closed){ $target->saveNBT(); $out[] = $target->namedtag; } } if(!empty($targets)){ $nbt->setData($out); $this->db->put($index, $nbt->write()); }else{ $this->db->delete($index); } } /** * @param int $x * @param int $z * @param bool $safe * * @return bool */ public function unloadChunk(int $x, int $z, bool $safe = true) : bool{ $chunk = $this->chunks[$index = Level::chunkHash($x, $z)] ?? null; if($chunk instanceof Chunk and $chunk->unload($safe)){ unset($this->chunks[$index]); return true; } return false; } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ public function saveChunk(int $chunkX, int $chunkZ) : bool{ if($this->isChunkLoaded($chunkX, $chunkZ)){ $chunk = $this->getChunk($chunkX, $chunkZ); if(!$chunk->isGenerated()){ throw new \InvalidStateException("Cannot save un-generated chunk"); } $this->writeChunk($chunk); return true; } return false; } /** * @param int $chunkX * @param int $chunkZ * @param bool $create * * @return Chunk|null */ public function getChunk(int $chunkX, int $chunkZ, bool $create = false){ $index = Level::chunkHash($chunkX, $chunkZ); if(isset($this->chunks[$index])){ return $this->chunks[$index]; }else{ $this->loadChunk($chunkX, $chunkZ, $create); return $this->chunks[$index] ?? null; } } /** * @return \LevelDB */ public function getDatabase(){ return $this->db; } /** * @param int $chunkX * @param int $chunkZ * @param Chunk $chunk */ public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk){ $chunk->setX($chunkX); $chunk->setZ($chunkZ); if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) and $this->chunks[$index] !== $chunk){ $this->unloadChunk($chunkX, $chunkZ, false); } $this->chunks[$index] = $chunk; } /** * @param int $chunkX * @param int $chunkZ * * @return string */ public static function chunkIndex(int $chunkX, int $chunkZ) : string{ return Binary::writeLInt($chunkX) . Binary::writeLInt($chunkZ); } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ private function chunkExists(int $chunkX, int $chunkZ) : bool{ return $this->db->get(LevelDB::chunkIndex($chunkX, $chunkZ) . self::TAG_VERSION) !== false; } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ public function isChunkGenerated(int $chunkX, int $chunkZ) : bool{ if($this->chunkExists($chunkX, $chunkZ) and ($chunk = $this->getChunk($chunkX, $chunkZ, false)) !== null){ return true; } return false; } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ public function isChunkPopulated(int $chunkX, int $chunkZ) : bool{ $chunk = $this->getChunk($chunkX, $chunkZ); if($chunk instanceof Chunk){ return $chunk->isPopulated(); }else{ return false; } } public function close(){ $this->unloadChunks(); $this->db->close(); $this->level = null; } }xPos = new IntTag("xPos", $chunk->getX()); $nbt->zPos = new IntTag("zPos", $chunk->getZ()); $nbt->V = new ByteTag("V", 1); $nbt->LastUpdate = new LongTag("LastUpdate", 0); //TODO $nbt->InhabitedTime = new LongTag("InhabitedTime", 0); //TODO $nbt->TerrainPopulated = new ByteTag("TerrainPopulated", $chunk->isPopulated()); $nbt->LightPopulated = new ByteTag("LightPopulated", $chunk->isLightPopulated()); $nbt->Sections = new ListTag("Sections", []); $nbt->Sections->setTagType(NBT::TAG_Compound); $subChunks = -1; foreach($chunk->getSubChunks() as $y => $subChunk){ if($subChunk->isEmpty()){ continue; } $nbt->Sections[++$subChunks] = new CompoundTag(null, [ "Y" => new ByteTag("Y", $y), "Blocks" => new ByteArrayTag("Blocks", ChunkUtils::reorderByteArray($subChunk->getBlockIdArray())), //Generic in-memory chunks are currently always XZY "Data" => new ByteArrayTag("Data", ChunkUtils::reorderNibbleArray($subChunk->getBlockDataArray())), "SkyLight" => new ByteArrayTag("SkyLight", ChunkUtils::reorderNibbleArray($subChunk->getSkyLightArray(), "\xff")), "BlockLight" => new ByteArrayTag("BlockLight", ChunkUtils::reorderNibbleArray($subChunk->getBlockLightArray())) ]); } $nbt->Biomes = new ByteArrayTag("Biomes", $chunk->getBiomeIdArray()); $nbt->HeightMap = new IntArrayTag("HeightMap", $chunk->getHeightMapArray()); $entities = []; foreach($chunk->getEntities() as $entity){ if(!($entity instanceof Player) and !$entity->closed){ $entity->saveNBT(); $entities[] = $entity->namedtag; } } $nbt->Entities = new ListTag("Entities", $entities); $nbt->Entities->setTagType(NBT::TAG_Compound); $tiles = []; foreach($chunk->getTiles() as $tile){ $tile->saveNBT(); $tiles[] = $tile->namedtag; } $nbt->TileEntities = new ListTag("TileEntities", $tiles); $nbt->TileEntities->setTagType(NBT::TAG_Compound); //TODO: TileTicks $writer = new NBT(NBT::BIG_ENDIAN); $nbt->setName("Level"); $writer->setData(new CompoundTag("", ["Level" => $nbt])); return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); } /** * @param string $data * * @return null|Chunk */ public function nbtDeserialize(string $data){ $nbt = new NBT(NBT::BIG_ENDIAN); try{ $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE); $chunk = $nbt->getData(); if(!isset($chunk->Level) or !($chunk->Level instanceof CompoundTag)){ throw new ChunkException("Invalid NBT format"); } $chunk = $chunk->Level; $subChunks = []; if($chunk->Sections instanceof ListTag){ foreach($chunk->Sections as $subChunk){ if($subChunk instanceof CompoundTag){ $subChunks[$subChunk->Y->getValue()] = new SubChunk( ChunkUtils::reorderByteArray($subChunk->Blocks->getValue()), ChunkUtils::reorderNibbleArray($subChunk->Data->getValue()), ChunkUtils::reorderNibbleArray($subChunk->SkyLight->getValue(), "\xff"), ChunkUtils::reorderNibbleArray($subChunk->BlockLight->getValue()) ); } } } if(isset($chunk->BiomeColors)){ $biomeIds = ChunkUtils::convertBiomeColors($chunk->BiomeColors->getValue()); //Convert back to original format }elseif(isset($chunk->Biomes)){ $biomeIds = $chunk->Biomes->getValue(); }else{ $biomeIds = ""; } $result = new Chunk( $chunk["xPos"], $chunk["zPos"], $subChunks, isset($chunk->Entities) ? $chunk->Entities->getValue() : [], isset($chunk->TileEntities) ? $chunk->TileEntities->getValue() : [], $biomeIds, isset($chunk->HeightMap) ? $chunk->HeightMap->getValue() : [] ); $result->setLightPopulated(isset($chunk->LightPopulated) ? ((bool) $chunk->LightPopulated->getValue()) : false); $result->setPopulated(isset($chunk->TerrainPopulated) ? ((bool) $chunk->TerrainPopulated->getValue()) : false); $result->setGenerated(true); return $result; }catch(\Throwable $e){ MainLogger::getLogger()->logException($e); return null; } } /** * @return string */ public static function getProviderName() : string{ return "anvil"; } /** * @return int */ public function getWorldHeight() : int{ //TODO: add world height options return 256; } }xPos = new IntTag("xPos", $chunk->getX()); $nbt->zPos = new IntTag("zPos", $chunk->getZ()); $nbt->V = new ByteTag("V", 0); //guess $nbt->LastUpdate = new LongTag("LastUpdate", 0); //TODO $nbt->InhabitedTime = new LongTag("InhabitedTime", 0); //TODO $nbt->TerrainPopulated = new ByteTag("TerrainPopulated", $chunk->isPopulated()); $nbt->LightPopulated = new ByteTag("LightPopulated", $chunk->isLightPopulated()); $ids = ""; $data = ""; $skyLight = ""; $blockLight = ""; $subChunks = $chunk->getSubChunks(); for($x = 0; $x < 16; ++$x){ for($z = 0; $z < 16; ++$z){ for($y = 0; $y < 8; ++$y){ $subChunk = $subChunks[$y]; $ids .= $subChunk->getBlockIdColumn($x, $z); $data .= $subChunk->getBlockDataColumn($x, $z); $skyLight .= $subChunk->getSkyLightColumn($x, $z); $blockLight .= $subChunk->getBlockLightColumn($x, $z); } } } $nbt->Blocks = new ByteArrayTag("Blocks", $ids); $nbt->Data = new ByteArrayTag("Data", $data); $nbt->SkyLight = new ByteArrayTag("SkyLight", $skyLight); $nbt->BlockLight = new ByteArrayTag("BlockLight", $blockLight); $nbt->Biomes = new ByteArrayTag("Biomes", $chunk->getBiomeIdArray()); $nbt->HeightMap = new ByteArrayTag("HeightMap", pack("C*", ...$chunk->getHeightMapArray())); $entities = []; foreach($chunk->getEntities() as $entity){ if(!($entity instanceof Player) and !$entity->closed){ $entity->saveNBT(); $entities[] = $entity->namedtag; } } $nbt->Entities = new ListTag("Entities", $entities); $nbt->Entities->setTagType(NBT::TAG_Compound); $tiles = []; foreach($chunk->getTiles() as $tile){ $tile->saveNBT(); $tiles[] = $tile->namedtag; } $nbt->TileEntities = new ListTag("TileEntities", $tiles); $nbt->TileEntities->setTagType(NBT::TAG_Compound); //TODO: TileTicks $writer = new NBT(NBT::BIG_ENDIAN); $nbt->setName("Level"); $writer->setData(new CompoundTag("", ["Level" => $nbt])); return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); } /** * @param string $data * * @return Chunk|null */ public function nbtDeserialize(string $data){ $nbt = new NBT(NBT::BIG_ENDIAN); try{ $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE); $chunk = $nbt->getData(); if(!isset($chunk->Level) or !($chunk->Level instanceof CompoundTag)){ throw new ChunkException("Invalid NBT format"); } $chunk = $chunk->Level; $subChunks = []; $fullIds = isset($chunk->Blocks) ? $chunk->Blocks->getValue() : str_repeat("\x00", 32768); $fullData = isset($chunk->Data) ? $chunk->Data->getValue() : (str_repeat("\x00", 16384)); $fullSkyLight = isset($chunk->SkyLight) ? $chunk->SkyLight->getValue() : str_repeat("\xff", 16384); $fullBlockLight = isset($chunk->BlockLight) ? $chunk->BlockLight->getValue() : (str_repeat("\x00", 16384)); for($y = 0; $y < 8; ++$y){ $offset = ($y << 4); $ids = ""; for($i = 0; $i < 256; ++$i){ $ids .= substr($fullIds, $offset, 16); $offset += 128; } $data = ""; $offset = ($y << 3); for($i = 0; $i < 256; ++$i){ $data .= substr($fullData, $offset, 8); $offset += 64; } $skyLight = ""; $offset = ($y << 3); for($i = 0; $i < 256; ++$i){ $skyLight .= substr($fullSkyLight, $offset, 8); $offset += 64; } $blockLight = ""; $offset = ($y << 3); for($i = 0; $i < 256; ++$i){ $blockLight .= substr($fullBlockLight, $offset, 8); $offset += 64; } $subChunks[$y] = new SubChunk($ids, $data, $skyLight, $blockLight); } if(isset($chunk->BiomeColors)){ $biomeIds = ChunkUtils::convertBiomeColors($chunk->BiomeColors->getValue()); //Convert back to original format }elseif(isset($chunk->Biomes)){ $biomeIds = $chunk->Biomes->getValue(); }else{ $biomeIds = ""; } $heightMap = []; if(isset($chunk->HeightMap)){ if($chunk->HeightMap instanceof ByteArrayTag){ $heightMap = array_values(unpack("C*", $chunk->HeightMap->getValue())); }elseif($chunk->HeightMap instanceof IntArrayTag){ $heightMap = $chunk->HeightMap->getValue(); #blameshoghicp } } $result = new Chunk( $chunk["xPos"], $chunk["zPos"], $subChunks, isset($chunk->Entities) ? $chunk->Entities->getValue() : [], isset($chunk->TileEntities) ? $chunk->TileEntities->getValue() : [], $biomeIds, $heightMap ); $result->setLightPopulated(isset($chunk->LightPopulated) ? ((bool) $chunk->LightPopulated->getValue()) : false); $result->setPopulated(isset($chunk->TerrainPopulated) ? ((bool) $chunk->TerrainPopulated->getValue()) : false); $result->setGenerated(true); return $result; }catch(\Throwable $e){ MainLogger::getLogger()->logException($e); return null; } } /** * @return string */ public static function getProviderName() : string{ return "mcregion"; } /** * @return int */ public function getWorldHeight() : int{ //TODO: add world height options return 128; } /** * @param string $path * * @return bool */ public static function isValid(string $path) : bool{ $isValid = (file_exists($path . "/level.dat") and is_dir($path . "/region/")); if($isValid){ $files = glob($path . "/region/*.mc*"); if(empty($files)){ //possible glob() issue on some systems $files = array_filter(scandir($path . "/region/"), function($file){ return substr($file, strrpos($file, ".") + 1, 2) === "mc"; //region file }); } foreach($files as $f){ if(substr($f, strrpos($f, ".") + 1) !== static::REGION_FILE_EXTENSION){ $isValid = false; break; } } } return $isValid; } /** * @param string $path * @param string $name * @param int|string $seed * @param string $generator * @param array $options */ public static function generate(string $path, string $name, $seed, string $generator, array $options = []){ if(!file_exists($path)){ mkdir($path, 0777, true); } if(!file_exists($path . "/region")){ mkdir($path . "/region", 0777); } //TODO, add extra details $levelData = new CompoundTag("Data", [ "hardcore" => new ByteTag("hardcore", 0), "initialized" => new ByteTag("initialized", 1), "GameType" => new IntTag("GameType", 0), "generatorVersion" => new IntTag("generatorVersion", 1), //2 in MCPE "SpawnX" => new IntTag("SpawnX", 128), "SpawnY" => new IntTag("SpawnY", 70), "SpawnZ" => new IntTag("SpawnZ", 128), "version" => new IntTag("version", 19133), "DayTime" => new IntTag("DayTime", 0), "LastPlayed" => new LongTag("LastPlayed", microtime(true) * 1000), "RandomSeed" => new LongTag("RandomSeed", $seed), "SizeOnDisk" => new LongTag("SizeOnDisk", 0), "Time" => new LongTag("Time", 0), "generatorName" => new StringTag("generatorName", Generator::getGeneratorName($generator)), "generatorOptions" => new StringTag("generatorOptions", isset($options["preset"]) ? $options["preset"] : ""), "LevelName" => new StringTag("LevelName", $name), "GameRules" => new CompoundTag("GameRules", []) ]); $nbt = new NBT(NBT::BIG_ENDIAN); $nbt->setData(new CompoundTag("", [ "Data" => $levelData ])); $buffer = $nbt->writeCompressed(); file_put_contents($path . "level.dat", $buffer); } /** * @return string */ public function getGenerator() : string{ return (string) $this->levelData["generatorName"]; } /** * @return array */ public function getGeneratorOptions() : array{ return ["preset" => $this->levelData["generatorOptions"]]; } /** * @param int $chunkX * @param int $chunkZ * @param bool $create * * @return null|Chunk */ public function getChunk(int $chunkX, int $chunkZ, bool $create = false){ $index = Level::chunkHash($chunkX, $chunkZ); if(isset($this->chunks[$index])){ return $this->chunks[$index]; }else{ $this->loadChunk($chunkX, $chunkZ, $create); return $this->chunks[$index] ?? null; } } /** * @param int $chunkX * @param int $chunkZ * @param Chunk $chunk */ public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk){ self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); $this->loadRegion($regionX, $regionZ); $chunk->setX($chunkX); $chunk->setZ($chunkZ); if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) and $this->chunks[$index] !== $chunk){ $this->unloadChunk($chunkX, $chunkZ, false); } $this->chunks[$index] = $chunk; } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ public function saveChunk(int $chunkX, int $chunkZ) : bool{ if($this->isChunkLoaded($chunkX, $chunkZ)){ $chunk = $this->getChunk($chunkX, $chunkZ); if(!$chunk->isGenerated()){ throw new \InvalidStateException("Cannot save un-generated chunk"); } $this->getRegion($chunkX >> 5, $chunkZ >> 5)->writeChunk($chunk); return true; } return false; } public function saveChunks(){ foreach($this->chunks as $chunk){ $this->saveChunk($chunk->getX(), $chunk->getZ()); } } /** * @param int $chunkX * @param int $chunkZ * @param bool $create * * @return bool */ public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : bool{ $index = Level::chunkHash($chunkX, $chunkZ); if(isset($this->chunks[$index])){ return true; } $regionX = $regionZ = null; self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); /** @noinspection PhpStrictTypeCheckingInspection */ $this->loadRegion($regionX, $regionZ); $this->level->timings->syncChunkLoadDataTimer->startTiming(); /** @noinspection PhpStrictTypeCheckingInspection */ $chunk = $this->getRegion($regionX, $regionZ)->readChunk($chunkX - $regionX * 32, $chunkZ - $regionZ * 32); if($chunk === null and $create){ $chunk = $this->getEmptyChunk($chunkX, $chunkZ); } $this->level->timings->syncChunkLoadDataTimer->stopTiming(); if($chunk !== null){ $this->chunks[$index] = $chunk; return true; }else{ return false; } } /** * @param int $chunkX * @param int $chunkZ * @param bool $safe * * @return bool */ public function unloadChunk(int $chunkX, int $chunkZ, bool $safe = true) : bool{ $chunk = $this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)] ?? null; if($chunk instanceof Chunk and $chunk->unload($safe)){ unset($this->chunks[$index]); return true; } return false; } public function unloadChunks(){ foreach($this->chunks as $chunk){ $this->unloadChunk($chunk->getX(), $chunk->getZ(), false); } $this->chunks = []; } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ public function isChunkLoaded(int $chunkX, int $chunkZ) : bool{ return isset($this->chunks[Level::chunkHash($chunkX, $chunkZ)]); } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ public function isChunkGenerated(int $chunkX, int $chunkZ) : bool{ if(($region = $this->getRegion($chunkX >> 5, $chunkZ >> 5)) !== null){ return $region->chunkExists($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32) and $this->getChunk($chunkX - $region->getX() * 32, $chunkZ - $region->getZ() * 32, true)->isGenerated(); } return false; } /** * @param int $chunkX * @param int $chunkZ * * @return bool */ public function isChunkPopulated(int $chunkX, int $chunkZ) : bool{ $chunk = $this->getChunk($chunkX, $chunkZ); if($chunk !== null){ return $chunk->isPopulated(); }else{ return false; } } /** * @return array */ public function getLoadedChunks() : array{ return $this->chunks; } public function doGarbageCollection(){ $limit = time() - 300; foreach($this->regions as $index => $region){ if($region->lastUsed <= $limit){ $region->close(); unset($this->regions[$index]); } } } /** * @param int $chunkX * @param int $chunkZ * @param int &$x * @param int &$z */ public static function getRegionIndex(int $chunkX, int $chunkZ, &$x, &$z){ $x = $chunkX >> 5; $z = $chunkZ >> 5; } /** * @param int $chunkX * @param int $chunkZ * * @return Chunk */ public function getEmptyChunk(int $chunkX, int $chunkZ){ return Chunk::getEmptyChunk($chunkX, $chunkZ); } /** * @param int $x * @param int $z * * @return RegionLoader */ protected function getRegion(int $x, int $z){ return $this->regions[Level::chunkHash($x, $z)] ?? null; } /** * @param int $x * @param int $z */ protected function loadRegion(int $x, int $z){ if(!isset($this->regions[$index = Level::chunkHash($x, $z)])){ $this->regions[$index] = new RegionLoader($this, $x, $z, static::REGION_FILE_EXTENSION); } } public function close(){ $this->unloadChunks(); foreach($this->regions as $index => $region){ $region->close(); unset($this->regions[$index]); } $this->level = null; } }xPos = new IntTag("xPos", $chunk->getX()); $nbt->zPos = new IntTag("zPos", $chunk->getZ()); $nbt->V = new ByteTag("V", 1); $nbt->LastUpdate = new LongTag("LastUpdate", 0); //TODO $nbt->InhabitedTime = new LongTag("InhabitedTime", 0); //TODO $nbt->TerrainPopulated = new ByteTag("TerrainPopulated", $chunk->isPopulated()); $nbt->LightPopulated = new ByteTag("LightPopulated", $chunk->isLightPopulated()); $nbt->Sections = new ListTag("Sections", []); $nbt->Sections->setTagType(NBT::TAG_Compound); $subChunks = -1; foreach($chunk->getSubChunks() as $y => $subChunk){ if($subChunk->isEmpty()){ continue; } $nbt->Sections[++$subChunks] = new CompoundTag(null, [ "Y" => new ByteTag("Y", $y), "Blocks" => new ByteArrayTag("Blocks", $subChunk->getBlockIdArray()), "Data" => new ByteArrayTag("Data", $subChunk->getBlockDataArray()), "SkyLight" => new ByteArrayTag("SkyLight", $subChunk->getSkyLightArray()), "BlockLight" => new ByteArrayTag("BlockLight", $subChunk->getBlockLightArray()) ]); } $nbt->Biomes = new ByteArrayTag("Biomes", $chunk->getBiomeIdArray()); $nbt->HeightMap = new IntArrayTag("HeightMap", $chunk->getHeightMapArray()); $entities = []; foreach($chunk->getEntities() as $entity){ if(!($entity instanceof Player) and !$entity->closed){ $entity->saveNBT(); $entities[] = $entity->namedtag; } } $nbt->Entities = new ListTag("Entities", $entities); $nbt->Entities->setTagType(NBT::TAG_Compound); $tiles = []; foreach($chunk->getTiles() as $tile){ $tile->saveNBT(); $tiles[] = $tile->namedtag; } $nbt->TileEntities = new ListTag("TileEntities", $tiles); $nbt->TileEntities->setTagType(NBT::TAG_Compound); //TODO: TileTicks $writer = new NBT(NBT::BIG_ENDIAN); $nbt->setName("Level"); $writer->setData(new CompoundTag("", ["Level" => $nbt])); return $writer->writeCompressed(ZLIB_ENCODING_DEFLATE, RegionLoader::$COMPRESSION_LEVEL); } /** * @param string $data * * @return null|Chunk */ public function nbtDeserialize(string $data){ $nbt = new NBT(NBT::BIG_ENDIAN); try{ $nbt->readCompressed($data, ZLIB_ENCODING_DEFLATE); $chunk = $nbt->getData(); if(!isset($chunk->Level) or !($chunk->Level instanceof CompoundTag)){ throw new ChunkException("Invalid NBT format"); } $chunk = $chunk->Level; $subChunks = []; if($chunk->Sections instanceof ListTag){ foreach($chunk->Sections as $subChunk){ if($subChunk instanceof CompoundTag){ $subChunks[$subChunk->Y->getValue()] = new SubChunk( $subChunk->Blocks->getValue(), $subChunk->Data->getValue(), $subChunk->SkyLight->getValue(), $subChunk->BlockLight->getValue() ); } } } $result = new Chunk( $chunk["xPos"], $chunk["zPos"], $subChunks, isset($chunk->Entities) ? $chunk->Entities->getValue() : [], isset($chunk->TileEntities) ? $chunk->TileEntities->getValue() : [], isset($chunk->Biomes) ? $chunk->Biomes->getValue() : "", isset($chunk->HeightMap) ? $chunk->HeightMap->getValue() : [] ); $result->setLightPopulated(isset($chunk->LightPopulated) ? ((bool) $chunk->LightPopulated->getValue()) : false); $result->setPopulated(isset($chunk->TerrainPopulated) ? ((bool) $chunk->TerrainPopulated->getValue()) : false); $result->setGenerated(true); return $result; }catch(\Throwable $e){ MainLogger::getLogger()->logException($e); return null; } } /** * @return string */ public static function getProviderName() : string{ return "pmanvil"; } }x = $regionX; $this->z = $regionZ; $this->levelProvider = $level; $this->filePath = $this->levelProvider->getPath() . "region/r.$regionX.$regionZ.$fileExtension"; $exists = file_exists($this->filePath); if(!$exists){ touch($this->filePath); } $this->filePointer = fopen($this->filePath, "r+b"); stream_set_read_buffer($this->filePointer, 1024 * 16); //16KB stream_set_write_buffer($this->filePointer, 1024 * 16); //16KB if(!$exists){ $this->createBlank(); }else{ $this->loadLocationTable(); } $this->lastUsed = time(); } public function __destruct(){ if(is_resource($this->filePointer)){ $this->writeLocationTable(); fclose($this->filePointer); } } /** * @param int $index * * @return bool */ protected function isChunkGenerated(int $index) : bool{ return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); } /** * @param int $x * @param int $z * * @return null|Chunk */ public function readChunk(int $x, int $z){ $index = self::getChunkOffset($x, $z); if($index < 0 or $index >= 4096){ return null; } $this->lastUsed = time(); if(!$this->isChunkGenerated($index)){ return null; } fseek($this->filePointer, $this->locationTable[$index][0] << 12); $length = Binary::readInt(fread($this->filePointer, 4)); $compression = ord(fgetc($this->filePointer)); if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted if($length >= self::MAX_SECTOR_LENGTH){ $this->locationTable[$index][0] = ++$this->lastSector; $this->locationTable[$index][1] = 1; MainLogger::getLogger()->error("Corrupted chunk header detected"); } return null; } if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors MainLogger::getLogger()->error("Corrupted bigger chunk detected"); $this->locationTable[$index][1] = $length >> 12; $this->writeLocationIndex($index); }elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){ MainLogger::getLogger()->error("Invalid compression type"); return null; } $chunk = $this->levelProvider->nbtDeserialize(fread($this->filePointer, $length - 1)); if($chunk instanceof Chunk){ return $chunk; }else{ MainLogger::getLogger()->error("Corrupted chunk detected"); return null; } } /** * @param int $x * @param int $z * * @return bool */ public function chunkExists(int $x, int $z) : bool{ return $this->isChunkGenerated(self::getChunkOffset($x, $z)); } /** * @param int $x * @param int $z * @param string $chunkData */ protected function saveChunk(int $x, int $z, string $chunkData){ $length = strlen($chunkData) + 1; if($length + 4 > self::MAX_SECTOR_LENGTH){ throw new ChunkException("Chunk is too big! " . ($length + 4) . " > " . self::MAX_SECTOR_LENGTH); } $sectors = (int) ceil(($length + 4) / 4096); $index = self::getChunkOffset($x, $z); $indexChanged = false; if($this->locationTable[$index][1] < $sectors){ $this->locationTable[$index][0] = $this->lastSector + 1; $this->lastSector += $sectors; //The GC will clean this shift "later" $indexChanged = true; }elseif($this->locationTable[$index][1] != $sectors){ $indexChanged = true; } $this->locationTable[$index][1] = $sectors; $this->locationTable[$index][2] = time(); fseek($this->filePointer, $this->locationTable[$index][0] << 12); fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $sectors << 12, "\x00", STR_PAD_RIGHT)); if($indexChanged){ $this->writeLocationIndex($index); } } /** * @param int $x * @param int $z */ public function removeChunk(int $x, int $z){ $index = self::getChunkOffset($x, $z); $this->locationTable[$index][0] = 0; $this->locationTable[$index][1] = 0; } /** * @param Chunk $chunk */ public function writeChunk(Chunk $chunk){ $this->lastUsed = time(); $chunkData = $this->levelProvider->nbtSerialize($chunk); if($chunkData !== false){ $this->saveChunk($chunk->getX() - ($this->getX() * 32), $chunk->getZ() - ($this->getZ() * 32), $chunkData); } } /** * @param int $x * @param int $z * * @return int */ protected static function getChunkOffset(int $x, int $z) : int{ return $x + ($z << 5); } public function close(){ $this->writeLocationTable(); fclose($this->filePointer); $this->levelProvider = null; } /** * @return int */ public function doSlowCleanUp() : int{ for($i = 0; $i < 1024; ++$i){ if($this->locationTable[$i][0] === 0 or $this->locationTable[$i][1] === 0){ continue; } fseek($this->filePointer, $this->locationTable[$i][0] << 12); $chunk = fread($this->filePointer, $this->locationTable[$i][1] << 12); $length = Binary::readInt(substr($chunk, 0, 4)); if($length <= 1){ $this->locationTable[$i] = [0, 0, 0]; //Non-generated chunk, remove it from index } try{ $chunk = zlib_decode(substr($chunk, 5)); }catch(\Throwable $e){ $this->locationTable[$i] = [0, 0, 0]; //Corrupted chunk, remove it continue; } $chunk = chr(self::COMPRESSION_ZLIB) . zlib_encode($chunk, ZLIB_ENCODING_DEFLATE, 9); $chunk = Binary::writeInt(strlen($chunk)) . $chunk; $sectors = (int) ceil(strlen($chunk) / 4096); if($sectors > $this->locationTable[$i][1]){ $this->locationTable[$i][0] = $this->lastSector + 1; $this->lastSector += $sectors; } fseek($this->filePointer, $this->locationTable[$i][0] << 12); fwrite($this->filePointer, str_pad($chunk, $sectors << 12, "\x00", STR_PAD_RIGHT)); } $this->writeLocationTable(); $n = $this->cleanGarbage(); $this->writeLocationTable(); return $n; } /** * @return int */ private function cleanGarbage() : int{ $sectors = []; foreach($this->locationTable as $index => $data){ //Calculate file usage if($data[0] === 0 or $data[1] === 0){ $this->locationTable[$index] = [0, 0, 0]; continue; } for($i = 0; $i < $data[1]; ++$i){ $sectors[$data[0]] = $index; } } if(count($sectors) === ($this->lastSector - 2)){ //No collection needed return 0; } ksort($sectors); $shift = 0; $lastSector = 1; //First chunk - 1 fseek($this->filePointer, 8192); $sector = 2; foreach($sectors as $sector => $index){ if(($sector - $lastSector) > 1){ $shift += $sector - $lastSector - 1; } if($shift > 0){ fseek($this->filePointer, $sector << 12); $old = fread($this->filePointer, 4096); fseek($this->filePointer, ($sector - $shift) << 12); fwrite($this->filePointer, $old, 4096); } $this->locationTable[$index][0] -= $shift; $lastSector = $sector; } ftruncate($this->filePointer, ($sector + 1) << 12); //Truncate to the end of file written return $shift; } protected function loadLocationTable(){ fseek($this->filePointer, 0); $this->lastSector = 1; $data = unpack("N*", fread($this->filePointer, 4 * 1024 * 2)); //1024 records * 4 bytes * 2 times for($i = 0; $i < 1024; ++$i){ $index = $data[$i + 1]; $this->locationTable[$i] = [$index >> 8, $index & 0xff, $data[1024 + $i + 1]]; if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){ $this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1; } } } private function writeLocationTable(){ $write = []; for($i = 0; $i < 1024; ++$i){ $write[] = (($this->locationTable[$i][0] << 8) | $this->locationTable[$i][1]); } for($i = 0; $i < 1024; ++$i){ $write[] = $this->locationTable[$i][2]; } fseek($this->filePointer, 0); fwrite($this->filePointer, pack("N*", ...$write), 4096 * 2); } /** * @param $index */ protected function writeLocationIndex($index){ fseek($this->filePointer, $index << 2); fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index][0] << 8) | $this->locationTable[$index][1]), 4); fseek($this->filePointer, 4096 + ($index << 2)); fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index][2]), 4); } protected function createBlank(){ fseek($this->filePointer, 0); ftruncate($this->filePointer, 0); $this->lastSector = 1; $table = ""; for($i = 0; $i < 1024; ++$i){ $this->locationTable[$i] = [0, 0]; $table .= Binary::writeInt(0); } $time = time(); for($i = 0; $i < 1024; ++$i){ $this->locationTable[$i][2] = $time; $table .= Binary::writeInt($time); } fwrite($this->filePointer, $table, 4096 * 2); } /** * @return int */ public function getX() : int{ return $this->x; } /** * @return int */ public function getZ() : int{ return $this->z; } } options; } /** * @return string */ public function getName() : string{ return "flat"; } /** * Flat constructor. * * @param array $options */ public function __construct(array $options = []){ $this->preset = "2;7,2x3,2;1;"; $this->options = $options; $this->chunk = null; if(isset($this->options["decoration"])){ $ores = new Ore(); $ores->setOreTypes([ new object\OreType(new CoalOre(), 20, 16, 0, 128), new object\OreType(New IronOre(), 20, 8, 0, 64), new object\OreType(new RedstoneOre(), 8, 7, 0, 16), new object\OreType(new LapisOre(), 1, 6, 0, 32), new object\OreType(new GoldOre(), 2, 8, 0, 32), new object\OreType(new DiamondOre(), 1, 7, 0, 16), new object\OreType(new Dirt(), 20, 32, 0, 128), new object\OreType(new Gravel(), 10, 16, 0, 128), ]); $this->populators[] = $ores; } } /** * @param string $layers * * @return array */ public static function parseLayers(string $layers) : array{ $result = []; preg_match_all('#^(([0-9]*x|)([0-9]{1,3})(|:[0-9]{0,2}))$#m', str_replace(",", "\n", $layers), $matches); $y = 0; foreach($matches[3] as $i => $b){ $b = Item::fromString($b . $matches[4][$i]); $cnt = $matches[2][$i] === "" ? 1 : intval($matches[2][$i]); for($cY = $y, $y += $cnt; $cY < $y; ++$cY){ $result[$cY] = [$b->getId(), $b->getDamage()]; } } return $result; } /** * @param $preset * @param $chunkX * @param $chunkZ */ protected function parsePreset($preset, $chunkX, $chunkZ){ $this->preset = $preset; $preset = explode(";", $preset); $blocks = $preset[1] ?? ""; $biome = $preset[2] ?? 1; $options = $preset[3] ?? ""; $this->structure = self::parseLayers($blocks); $this->chunks = []; $this->floorLevel = $y = count($this->structure); for(; $y < 0xFF; ++$y){ $this->structure[$y] = [0, 0]; } $this->chunk = clone $this->level->getChunk($chunkX, $chunkZ); $this->chunk->setGenerated(); for($Z = 0; $Z < 16; ++$Z){ for($X = 0; $X < 16; ++$X){ $this->chunk->setBiomeId($X, $Z, $biome); for($y = 0; $y < 128; ++$y){ $this->chunk->setBlock($X, $y, $Z, ...$this->structure[$y]); } } } preg_match_all('#(([0-9a-z_]{1,})\(?([0-9a-z_ =:]{0,})\)?),?#', $options, $matches); foreach($matches[2] as $i => $option){ $params = true; if($matches[3][$i] !== ""){ $params = []; $p = explode(" ", $matches[3][$i]); foreach($p as $k){ $k = explode("=", $k); if(isset($k[1])){ $params[$k[0]] = $k[1]; } } } $this->options[$option] = $params; } } /** * @param ChunkManager $level * @param Random $random * * @return mixed|void */ public function init(ChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; /* // Commented out : We want to delay this if(isset($this->options["preset"]) and $this->options["preset"] != ""){ $this->parsePreset($this->options["preset"]); }else{ $this->parsePreset($this->preset); } */ } /** * @param $chunkX * @param $chunkZ * * @return mixed|void */ public function generateChunk($chunkX, $chunkZ){ if($this->chunk === null){ if(isset($this->options["preset"]) and $this->options["preset"] != ""){ $this->parsePreset($this->options["preset"], $chunkX, $chunkZ); }else{ $this->parsePreset($this->preset, $chunkX, $chunkZ); } } $chunk = clone $this->chunk; $chunk->setX($chunkX); $chunk->setZ($chunkZ); $this->level->setChunk($chunkX, $chunkZ, $chunk); } /** * @param $chunkX * @param $chunkZ * * @return mixed|void */ public function populateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); foreach($this->populators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } } /** * @return Vector3 */ public function getSpawn(){ return new Vector3(128, $this->floorLevel, 128); } } state = true; $this->levelId = $level->getId(); $this->chunk = $chunk->fastSerialize(); } public function onRun(){ /** @var SimpleChunkManager $manager */ $manager = $this->getFromThreadStore("generation.level{$this->levelId}.manager"); /** @var Generator $generator */ $generator = $this->getFromThreadStore("generation.level{$this->levelId}.generator"); if($manager === null or $generator === null){ $this->state = false; return; } /** @var Chunk $chunk */ $chunk = Chunk::fastDeserialize($this->chunk); if($chunk === null){ //TODO error return; } $manager->setChunk($chunk->getX(), $chunk->getZ(), $chunk); $generator->generateChunk($chunk->getX(), $chunk->getZ()); $chunk = $manager->getChunk($chunk->getX(), $chunk->getZ()); $chunk->setGenerated(); $this->chunk = $chunk->fastSerialize(); $manager->setChunk($chunk->getX(), $chunk->getZ(), null); } /** * @param Server $server */ public function onCompletion(Server $server){ $level = $server->getLevel($this->levelId); if($level !== null){ if($this->state === false){ $level->registerGenerator(); return; } /** @var Chunk $chunk */ $chunk = Chunk::fastDeserialize($this->chunk); if($chunk === null){ //TODO error return; } $level->generateChunkCallback($chunk->getX(), $chunk->getZ(), $chunk); } } } $c){ if($c === $class){ return $name; } } return "unknown"; } /** * @param Noise $noise * @param int $xSize * @param int $samplingRate * @param int $x * @param int $y * @param int $z * * @return \SplFixedArray */ public static function getFastNoise1D(Noise $noise, $xSize, $samplingRate, $x, $y, $z){ if($samplingRate === 0){ throw new \InvalidArgumentException("samplingRate cannot be 0"); } if($xSize % $samplingRate !== 0){ throw new \InvalidArgumentCountException("xSize % samplingRate must return 0"); } $noiseArray = new \SplFixedArray($xSize + 1); for($xx = 0; $xx <= $xSize; $xx += $samplingRate){ $noiseArray[$xx] = $noise->noise3D($xx + $x, $y, $z); } for($xx = 0; $xx < $xSize; ++$xx){ if($xx % $samplingRate !== 0){ $nx = (int) ($xx / $samplingRate) * $samplingRate; $noiseArray[$xx] = Noise::linearLerp($xx, $nx, $nx + $samplingRate, $noiseArray[$nx], $noiseArray[$nx + $samplingRate]); } } return $noiseArray; } /** * @param Noise $noise * @param int $xSize * @param int $zSize * @param int $samplingRate * @param int $x * @param int $y * @param int $z * * @return \SplFixedArray */ public static function getFastNoise2D(Noise $noise, $xSize, $zSize, $samplingRate, $x, $y, $z){ if($samplingRate === 0){ throw new \InvalidArgumentException("samplingRate cannot be 0"); } if($xSize % $samplingRate !== 0){ throw new \InvalidArgumentCountException("xSize % samplingRate must return 0"); } if($zSize % $samplingRate !== 0){ throw new \InvalidArgumentCountException("zSize % samplingRate must return 0"); } $noiseArray = new \SplFixedArray($xSize + 1); for($xx = 0; $xx <= $xSize; $xx += $samplingRate){ $noiseArray[$xx] = new \SplFixedArray($zSize + 1); for($zz = 0; $zz <= $zSize; $zz += $samplingRate){ $noiseArray[$xx][$zz] = $noise->noise3D($x + $xx, $y, $z + $zz); } } for($xx = 0; $xx < $xSize; ++$xx){ if($xx % $samplingRate !== 0){ $noiseArray[$xx] = new \SplFixedArray($zSize + 1); } for($zz = 0; $zz < $zSize; ++$zz){ if($xx % $samplingRate !== 0 or $zz % $samplingRate !== 0){ $nx = (int) ($xx / $samplingRate) * $samplingRate; $nz = (int) ($zz / $samplingRate) * $samplingRate; $noiseArray[$xx][$zz] = Noise::bilinearLerp( $xx, $zz, $noiseArray[$nx][$nz], $noiseArray[$nx][$nz + $samplingRate], $noiseArray[$nx + $samplingRate][$nz], $noiseArray[$nx + $samplingRate][$nz + $samplingRate], $nx, $nx + $samplingRate, $nz, $nz + $samplingRate ); } } } return $noiseArray; } /** * @param Noise $noise * @param int $xSize * @param int $ySize * @param int $zSize * @param int $xSamplingRate * @param int $ySamplingRate * @param int $zSamplingRate * @param int $x * @param int $y * @param int $z * * @return \SplFixedArray */ public static function getFastNoise3D(Noise $noise, $xSize, $ySize, $zSize, $xSamplingRate, $ySamplingRate, $zSamplingRate, $x, $y, $z){ if($xSamplingRate === 0){ throw new \InvalidArgumentException("xSamplingRate cannot be 0"); } if($zSamplingRate === 0){ throw new \InvalidArgumentException("zSamplingRate cannot be 0"); } if($ySamplingRate === 0){ throw new \InvalidArgumentException("ySamplingRate cannot be 0"); } if($xSize % $xSamplingRate !== 0){ throw new \InvalidArgumentCountException("xSize % xSamplingRate must return 0"); } if($zSize % $zSamplingRate !== 0){ throw new \InvalidArgumentCountException("zSize % zSamplingRate must return 0"); } if($ySize % $ySamplingRate !== 0){ throw new \InvalidArgumentCountException("ySize % ySamplingRate must return 0"); } $noiseArray = array_fill(0, $xSize + 1, array_fill(0, $zSize + 1, [])); for($xx = 0; $xx <= $xSize; $xx += $xSamplingRate){ for($zz = 0; $zz <= $zSize; $zz += $zSamplingRate){ for($yy = 0; $yy <= $ySize; $yy += $ySamplingRate){ $noiseArray[$xx][$zz][$yy] = $noise->noise3D($x + $xx, $y + $yy, $z + $zz, true); } } } for($xx = 0; $xx < $xSize; ++$xx){ for($zz = 0; $zz < $zSize; ++$zz){ for($yy = 0; $yy < $ySize; ++$yy){ if($xx % $xSamplingRate !== 0 or $zz % $zSamplingRate !== 0 or $yy % $ySamplingRate !== 0){ $nx = (int) ($xx / $xSamplingRate) * $xSamplingRate; $ny = (int) ($yy / $ySamplingRate) * $ySamplingRate; $nz = (int) ($zz / $zSamplingRate) * $zSamplingRate; $nnx = $nx + $xSamplingRate; $nny = $ny + $ySamplingRate; $nnz = $nz + $zSamplingRate; $dx1 = (($nnx - $xx) / ($nnx - $nx)); $dx2 = (($xx - $nx) / ($nnx - $nx)); $dy1 = (($nny - $yy) / ($nny - $ny)); $dy2 = (($yy - $ny) / ($nny - $ny)); $noiseArray[$xx][$zz][$yy] = (($nnz - $zz) / ($nnz - $nz)) * ( $dy1 * ( $dx1 * $noiseArray[$nx][$nz][$ny] + $dx2 * $noiseArray[$nnx][$nz][$ny] ) + $dy2 * ( $dx1 * $noiseArray[$nx][$nz][$nny] + $dx2 * $noiseArray[$nnx][$nz][$nny] ) ) + (($zz - $nz) / ($nnz - $nz)) * ( $dy1 * ( $dx1 * $noiseArray[$nx][$nnz][$ny] + $dx2 * $noiseArray[$nnx][$nnz][$ny] ) + $dy2 * ( $dx1 * $noiseArray[$nx][$nnz][$nny] + $dx2 * $noiseArray[$nnx][$nnz][$nny] ) ); } } } } return $noiseArray; } /** * @return int */ public function getWaterHeight() : int{ return 0; } /** * Generator constructor. * * @param array $settings */ public abstract function __construct(array $settings = []); /** * @param ChunkManager $level * @param Random $random * * @return mixed */ public abstract function init(ChunkManager $level, Random $random); /** * @param $chunkX * @param $chunkZ * * @return mixed */ public abstract function generateChunk($chunkX, $chunkZ); /** * @param $chunkX * @param $chunkZ * * @return mixed */ public abstract function populateChunk($chunkX, $chunkZ); public abstract function getSettings(); public abstract function getName(); public abstract function getSpawn(); } generator = get_class($generator); $this->waterHeight = $generator->getWaterHeight(); $this->settings = serialize($generator->getSettings()); $this->seed = $level->getSeed(); $this->levelId = $level->getId(); } public function onRun(){ Block::init(); Biome::init(); $manager = new SimpleChunkManager($this->seed, $this->waterHeight); $this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager); /** @var Generator $generator */ $generator = $this->generator; $generator = new $generator(unserialize($this->settings)); $generator->init($manager, new Random($manager->getSeed())); $this->saveToThreadStore("generation.level{$this->levelId}.generator", $generator); } } levelId = $level->getId(); } public function onRun(){ $this->saveToThreadStore("generation.level{$this->levelId}.manager", null); $this->saveToThreadStore("generation.level{$this->levelId}.generator", null); } } levelId = $level->getId(); $this->chunk = $chunk->fastSerialize(); } public function onRun(){ /** @var Chunk $chunk */ $chunk = Chunk::fastDeserialize($this->chunk); if($chunk === null){ //TODO error return; } $chunk->recalculateHeightMap(); $chunk->populateSkyLight(); $chunk->setLightPopulated(); $this->chunk = $chunk->fastSerialize(); } /** * @param Server $server */ public function onCompletion(Server $server){ $level = $server->getLevel($this->levelId); if($level !== null){ /** @var Chunk $chunk */ $chunk = Chunk::fastDeserialize($this->chunk); if($chunk === null){ //TODO error return; } $level->generateChunkCallback($chunk->getX(), $chunk->getZ(), $chunk); } } } state = true; $this->levelId = $level->getId(); $this->chunk = $chunk->fastSerialize(); for($i = 0; $i < 9; ++$i){ if($i === 4){ continue; } $xx = -1 + $i % 3; $zz = -1 + (int) ($i / 3); $ck = $level->getChunk($chunk->getX() + $xx, $chunk->getZ() + $zz, false); $this->{"chunk$i"} = $ck !== null ? $ck->fastSerialize() : null; } } public function onRun(){ /** @var SimpleChunkManager $manager */ $manager = $this->getFromThreadStore("generation.level{$this->levelId}.manager"); /** @var Generator $generator */ $generator = $this->getFromThreadStore("generation.level{$this->levelId}.generator"); if($manager === null or $generator === null){ $this->state = false; return; } /** @var Chunk[] $chunks */ $chunks = []; $chunk = Chunk::fastDeserialize($this->chunk); for($i = 0; $i < 9; ++$i){ if($i === 4){ continue; } $xx = -1 + $i % 3; $zz = -1 + (int) ($i / 3); $ck = $this->{"chunk$i"}; if($ck === null){ $chunks[$i] = Chunk::getEmptyChunk($chunk->getX() + $xx, $chunk->getZ() + $zz); }else{ $chunks[$i] = Chunk::fastDeserialize($ck); } } if($chunk === null){ //TODO error return; } $manager->setChunk($chunk->getX(), $chunk->getZ(), $chunk); if(!$chunk->isGenerated()){ $generator->generateChunk($chunk->getX(), $chunk->getZ()); $chunk->setGenerated(); } foreach($chunks as $c){ if($c !== null){ $manager->setChunk($c->getX(), $c->getZ(), $c); if(!$c->isGenerated()){ $generator->generateChunk($c->getX(), $c->getZ()); $c = $manager->getChunk($c->getX(), $c->getZ()); $c->setGenerated(); } } } $generator->populateChunk($chunk->getX(), $chunk->getZ()); $chunk = $manager->getChunk($chunk->getX(), $chunk->getZ()); $chunk->recalculateHeightMap(); $chunk->populateSkyLight(); $chunk->setLightPopulated(); $chunk->setPopulated(); $this->chunk = $chunk->fastSerialize(); $manager->setChunk($chunk->getX(), $chunk->getZ(), null); foreach($chunks as $i => $c){ if($c !== null){ $c = $chunks[$i] = $manager->getChunk($c->getX(), $c->getZ()); if(!$c->hasChanged()){ $chunks[$i] = null; } }else{ //This way non-changed chunks are not set $chunks[$i] = null; } } $manager->cleanChunks(); for($i = 0; $i < 9; ++$i){ if($i === 4){ continue; } $this->{"chunk$i"} = $chunks[$i] !== null ? $chunks[$i]->fastSerialize() : null; } } /** * @param Server $server */ public function onCompletion(Server $server){ $level = $server->getLevel($this->levelId); if($level !== null){ if($this->state === false){ $level->registerGenerator(); return; } $chunk = Chunk::fastDeserialize($this->chunk); if($chunk === null){ //TODO error return; } for($i = 0; $i < 9; ++$i){ if($i === 4){ continue; } $c = $this->{"chunk$i"}; if($c !== null){ $c = Chunk::fastDeserialize($c); $level->generateChunkCallback($c->getX(), $c->getZ(), $c); } } $level->generateChunkCallback($chunk->getX(), $chunk->getZ(), $chunk); } } } options = $settings; } /** * @param ChunkManager $level * @param Random $random * * @return mixed|void */ public function init(ChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; } /** * @param $chunkX * @param $chunkZ * * @return mixed|void */ public function generateChunk($chunkX, $chunkZ){ if($this->emptyChunk === null){ $this->chunk = clone $this->level->getChunk($chunkX, $chunkZ); $this->chunk->setGenerated(); for($Z = 0; $Z < 16; ++$Z){ for($X = 0; $X < 16; ++$X){ $this->chunk->setBiomeId($X, $Z, 1); for($y = 0; $y < 128; ++$y){ $this->chunk->setBlockId($X, $y, $Z, Block::AIR); } } } $spawn = $this->getSpawn(); if($spawn->getX() >> 4 === $chunkX and $spawn->getZ() >> 4 === $chunkZ){ $this->chunk->setBlockId(0, 64, 0, Block::GRASS); }else{ $this->emptyChunk = clone $this->chunk; } }else{ $this->chunk = clone $this->emptyChunk; } $chunk = clone $this->chunk; $chunk->setX($chunkX); $chunk->setZ($chunkZ); $this->level->setChunk($chunkX, $chunkZ, $chunk); } /** * @param $chunkX * @param $chunkZ * * @return mixed|void */ public function populateChunk($chunkX, $chunkZ){ } /** * @return Vector3 */ public function getSpawn(){ return new Vector3(128, 72, 128); } } setId((int) $id); $flowerPopFound = false; foreach($biome->getPopulators() as $populator){ if($populator instanceof Flower){ $flowerPopFound = true; break; } } if($flowerPopFound === false){ $flower = new Flower(); $biome->addPopulator($flower); } } public static function init(){ self::register(self::OCEAN, new OceanBiome()); self::register(self::PLAINS, new PlainBiome()); self::register(self::DESERT, new DesertBiome()); self::register(self::MOUNTAINS, new MountainsBiome()); self::register(self::FOREST, new ForestBiome()); self::register(self::TAIGA, new TaigaBiome()); self::register(self::SWAMP, new SwampBiome()); self::register(self::RIVER, new RiverBiome()); self::register(self::BEACH, new BeachBiome()); self::register(self::MESA, new MesaBiome()); self::register(self::ICE_PLAINS, new IcePlainsBiome()); self::register(self::SMALL_MOUNTAINS, new SmallMountainsBiome()); self::register(self::HELL, new HellBiome()); self::register(self::BIRCH_FOREST, new ForestBiome(ForestBiome::TYPE_BIRCH)); } /** * @param $id * * @return Biome */ public static function getBiome($id){ return isset(self::$biomes[$id]) ? self::$biomes[$id] : self::$biomes[self::OCEAN]; } public function clearPopulators(){ $this->populators = []; } /** * @param Populator $populator */ public function addPopulator(Populator $populator){ $this->populators[get_class($populator)] = $populator; } /** * @param $class */ public function removePopulator($class){ if(isset($this->populators[$class])){ unset($this->populators[$class]); } } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random */ public function populateChunk(ChunkManager $level, $chunkX, $chunkZ, Random $random){ foreach($this->populators as $populator){ $populator->populate($level, $chunkX, $chunkZ, $random); } } /** * @return Populator[] */ public function getPopulators(){ return $this->populators; } /** * @param $id */ public function setId($id){ if(!$this->registered){ $this->registered = true; $this->id = $id; } } public function getId(){ return $this->id; } public abstract function getName(); public function getMinElevation(){ return $this->minElevation; } public function getMaxElevation(){ return $this->maxElevation; } /** * @param $min * @param $max */ public function setElevation($min, $max){ $this->minElevation = $min; $this->maxElevation = $max; } /** * @return Block[] */ public function getGroundCover(){ return $this->groundCover; } /** * @param Block[] $covers */ public function setGroundCover(array $covers){ $this->groundCover = $covers; } /** * @return float */ public function getTemperature(){ return $this->temperature; } /** * @return float */ public function getRainfall(){ return $this->rainfall; } }fallback = $fallback; $this->lookup = $lookup; $this->temperature = new Simplex($random, 2, 1 / 16, 1 / 512); $this->rainfall = new Simplex($random, 2, 1 / 16, 1 / 512); } public function recalculate(){ $this->map = new \SplFixedArray(64 * 64); for($i = 0; $i < 64; ++$i){ for($j = 0; $j < 64; ++$j){ $this->map[$i + ($j << 6)] = call_user_func($this->lookup, $i / 63, $j / 63); } } } public function addBiome(Biome $biome){ $this->biomes[$biome->getId()] = $biome; } public function getTemperature($x, $z){ return ($this->temperature->noise2D($x, $z, true) + 1) / 2; } public function getRainfall($x, $z){ return ($this->rainfall->noise2D($x, $z, true) + 1) / 2; } /** * @param $x * @param $z * * @return Biome */ public function pickBiome($x, $z){ $temperature = (int) ($this->getTemperature($x, $z) * 63); $rainfall = (int) ($this->getRainfall($x, $z) * 63); $biomeId = $this->map[$temperature + ($rainfall << 6)]; return isset($this->biomes[$biomeId]) ? $this->biomes[$biomeId] : $this->fallback; } }waterHeight; } public function getSettings() { return []; } public function init(ChunkManager $level, Random $random) { $this->level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); $this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 64); $this->random->setSeed($this->level->getSeed()); $pilar = new EnderPilar(); $pilar->setBaseAmount(0); $pilar->setRandomAmount(0); $this->populators[] = $pilar; } public function generateChunk($chunkX, $chunkZ) { $this->random->setSeed(0xa6fe78dc ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); $noise = Generator::getFastNoise3D($this->noiseBase, 16, 128, 16, 4, 8, 4, $chunkX * 16, 0, $chunkZ * 16); $chunk = $this->level->getChunk($chunkX, $chunkZ); for ($x = 0; $x < 16; ++$x) { for ($z = 0; $z < 16; ++$z) { $biome = Biome::getBiome(Biome::END); $biome->setGroundCover([ Block::get(Block::OBSIDIAN, 0) ]); $chunk->setBiomeId($x, $z, $biome->getId()); $color = [0, 0, 0]; $bColor = 2; $color[0] += (($bColor >> 16) ** 2); $color[1] += ((($bColor >> 8) & 0xff) ** 2); $color[2] += (($bColor & 0xff) ** 2); //$chunk->setBiomeColor($x, $z, $color[0], $color[1], $color[2]); for ($y = 0; $y < 128; ++$y) { $noiseValue = (abs($this->emptyHeight - $y) / $this->emptyHeight) * $this->emptyAmplitude - $noise[$x][$z][$y]; $noiseValue -= 1 - $this->density; $distance = new Vector3(0, 64, 0); $distance = $distance->distance(new Vector3($chunkX * 16 + $x, ($y / 1.3), $chunkZ * 16 + $z)); if ($noiseValue < 0 && $distance < 100 or $noiseValue < -0.2 && $distance > 400) { $chunk->setBlockId($x, $y, $z, Block::END_STONE); } } } } foreach ($this->generationPopulators as $populator) { $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } } public function populateChunk($chunkX, $chunkZ) { $this->random->setSeed(0xa6fe78dc ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); foreach ($this->populators as $populator) { $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } $chunk = $this->level->getChunk($chunkX, $chunkZ); $biome = Biome::getBiome($chunk->getBiomeId(7, 7)); $biome->populateChunk($this->level, $chunkX, $chunkZ, $this->random); } public function getSpawn() { return new Vector3(48, 128, 48); } }randomAmount = $amount; } public function setBaseAmount($amount) { $this->baseAmount = $amount; } public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random) { if (mt_rand(0, 100) < 10) { $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for ($i = 0; $i < $amount; ++$i) { $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if ($this->level->getBlockIdAt($x, $y, $z) == Block::END_STONE) { $height = mt_rand(28, 50); for ($ny = $y; $ny < $y + $height; $ny++) { for ($r = 0.5; $r < 5; $r += 0.5) { $nd = 360 / (2 * pi() * $r); for ($d = 0; $d < 360; $d += $nd) { $level->setBlockIdAt($x + (cos(deg2rad($d)) * $r), $ny, $z + (sin(deg2rad($d)) * $r), Block::OBSIDIAN); } } } } } } } private function getHighestWorkableBlock($x, $z) { for ($y = 127; $y >= 0; --$y) { $b = $this->level->getBlockIdAt($x, $y, $z); if ($b == Block::END_STONE) { break; } } return $y === 0 ? -1 : $y; } }waterHeight; } /** * @return array */ public function getSettings(){ return []; } /** * @param ChunkManager $level * @param Random $random * * @return mixed|void */ public function init(ChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); $this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 64); $this->random->setSeed($this->level->getSeed()); $ores = new NetherOre(); $ores->setOreTypes([ new OreType(new NetherQuartzOre(), 20, 16, 0, 128), new OreType(new SoulSand(), 5, 64, 0, 128), new OreType(new Gravel(), 5, 64, 0, 128), new OreType(new Lava(), 1, 16, 0, $this->waterHeight), ]); $this->populators[] = $ores; $this->populators[] = new NetherGlowStone(); $groundFire = new GroundFire(); $groundFire->setBaseAmount(1); $groundFire->setRandomAmount(1); $this->populators[] = $groundFire; $lava = new NetherLava(); $lava->setBaseAmount(0); $lava->setRandomAmount(0); $this->populators[] = $lava; } /** * @param $chunkX * @param $chunkZ * * @return mixed|void */ public function generateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); $noise = Generator::getFastNoise3D($this->noiseBase, 16, 128, 16, 4, 8, 4, $chunkX * 16, 0, $chunkZ * 16); $chunk = $this->level->getChunk($chunkX, $chunkZ); for($x = 0; $x < 16; ++$x){ for($z = 0; $z < 16; ++$z){ $biome = Biome::getBiome(Biome::HELL); $chunk->setBiomeId($x, $z, $biome->getId()); for($y = 0; $y < 128; ++$y){ if($y === 0 or $y === 127){ $chunk->setBlockId($x, $y, $z, Block::BEDROCK); continue; } $noiseValue = (abs($this->emptyHeight - $y) / $this->emptyHeight) * $this->emptyAmplitude - $noise[$x][$z][$y]; $noiseValue -= 1 - $this->density; if($noiseValue > 0){ $chunk->setBlockId($x, $y, $z, Block::NETHERRACK); }elseif($y <= $this->waterHeight){ $chunk->setBlockId($x, $y, $z, Block::STILL_LAVA); $chunk->setBlockLight($x, $y + 1, $z, 15); } } } } foreach($this->generationPopulators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } } /** * @param $chunkX * @param $chunkZ * * @return mixed|void */ public function populateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); foreach($this->populators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } $chunk = $this->level->getChunk($chunkX, $chunkZ); $biome = Biome::getBiome($chunk->getBiomeId(7, 7)); $biome->populateChunk($this->level, $chunkX, $chunkZ, $this->random); } /** * @return Vector3 */ public function getSpawn(){ return new Vector3(127.5, 128, 127.5); } }= 0 ? (int) $x : (int) ($x - 1); } /** * @param $x * * @return mixed */ public static function fade($x){ return $x * $x * $x * ($x * ($x * 6 - 15) + 10); } /** * @param $x * @param $y * @param $z * * @return mixed */ public static function lerp($x, $y, $z){ return $y + $x * ($z - $y); } /** * @param $x * @param $x1 * @param $x2 * @param $q0 * @param $q1 * * @return float|int */ public static function linearLerp($x, $x1, $x2, $q0, $q1){ return (($x2 - $x) / ($x2 - $x1)) * $q0 + (($x - $x1) / ($x2 - $x1)) * $q1; } /** * @param $x * @param $y * @param $q00 * @param $q01 * @param $q10 * @param $q11 * @param $x1 * @param $x2 * @param $y1 * @param $y2 * * @return float|int */ public static function bilinearLerp($x, $y, $q00, $q01, $q10, $q11, $x1, $x2, $y1, $y2){ $dx1 = (($x2 - $x) / ($x2 - $x1)); $dx2 = (($x - $x1) / ($x2 - $x1)); return (($y2 - $y) / ($y2 - $y1)) * ( $dx1 * $q00 + $dx2 * $q10 ) + (($y - $y1) / ($y2 - $y1)) * ( $dx1 * $q01 + $dx2 * $q11 ); } /** * @param $x * @param $y * @param $z * @param $q000 * @param $q001 * @param $q010 * @param $q011 * @param $q100 * @param $q101 * @param $q110 * @param $q111 * @param $x1 * @param $x2 * @param $y1 * @param $y2 * @param $z1 * @param $z2 * * @return float|int */ public static function trilinearLerp($x, $y, $z, $q000, $q001, $q010, $q011, $q100, $q101, $q110, $q111, $x1, $x2, $y1, $y2, $z1, $z2){ $dx1 = (($x2 - $x) / ($x2 - $x1)); $dx2 = (($x - $x1) / ($x2 - $x1)); $dy1 = (($y2 - $y) / ($y2 - $y1)); $dy2 = (($y - $y1) / ($y2 - $y1)); return (($z2 - $z) / ($z2 - $z1)) * ( $dy1 * ( $dx1 * $q000 + $dx2 * $q100 ) + $dy2 * ( $dx1 * $q001 + $dx2 * $q101 ) ) + (($z - $z1) / ($z2 - $z1)) * ( $dy1 * ( $dx1 * $q010 + $dx2 * $q110 ) + $dy2 * ( $dx1 * $q011 + $dx2 * $q111 ) ); } /** * @param $hash * @param $x * @param $y * @param $z * * @return mixed */ public static function grad($hash, $x, $y, $z){ $hash &= 15; $u = $hash < 8 ? $x : $y; $v = $hash < 4 ? $y : (($hash === 12 or $hash === 14) ? $x : $z); return (($hash & 1) === 0 ? $u : -$u) + (($hash & 2) === 0 ? $v : -$v); } /** * @param $x * @param $z * * @return mixed */ abstract public function getNoise2D($x, $z); /** * @param $x * @param $y * @param $z * * @return mixed */ abstract public function getNoise3D($x, $y, $z); /** * @param $x * @param $z * @param bool $normalized * * @return int */ public function noise2D($x, $z, $normalized = false){ $result = 0; $amp = 1; $freq = 1; $max = 0; $x *= $this->expansion; $z *= $this->expansion; for($i = 0; $i < $this->octaves; ++$i){ $result += $this->getNoise2D($x * $freq, $z * $freq) * $amp; $max += $amp; $freq *= 2; $amp *= $this->persistence; } if($normalized === true){ $result /= $max; } return $result; } /** * @param $x * @param $y * @param $z * @param bool $normalized * * @return int */ public function noise3D($x, $y, $z, $normalized = false){ $result = 0; $amp = 1; $freq = 1; $max = 0; $x *= $this->expansion; $y *= $this->expansion; $z *= $this->expansion; for($i = 0; $i < $this->octaves; ++$i){ $result += $this->getNoise3D($x * $freq, $y * $freq, $z * $freq) * $amp; $max += $amp; $freq *= 2; $amp *= $this->persistence; } if($normalized === true){ $result /= $max; } return $result; } /** * @param $x * @param $y * @param $z */ public function setOffset($x, $y, $z){ $this->offsetX = $x; $this->offsetY = $y; $this->offsetZ = $z; } }octaves = $octaves; $this->persistence = $persistence; $this->expansion = $expansion; $this->offsetX = $random->nextFloat() * 256; $this->offsetY = $random->nextFloat() * 256; $this->offsetZ = $random->nextFloat() * 256; for($i = 0; $i < 512; ++$i){ $this->perm[$i] = 0; } for($i = 0; $i < 256; ++$i){ $this->perm[$i] = $random->nextBoundedInt(256); } for($i = 0; $i < 256; ++$i){ $pos = $random->nextBoundedInt(256 - $i) + $i; $old = $this->perm[$i]; $this->perm[$i] = $this->perm[$pos]; $this->perm[$pos] = $old; $this->perm[$i + 256] = $this->perm[$i]; } } /** * @param $x * @param $y * @param $z * * @return mixed */ public function getNoise3D($x, $y, $z){ $x += $this->offsetX; $y += $this->offsetY; $z += $this->offsetZ; $floorX = (int) $x; $floorY = (int) $y; $floorZ = (int) $z; $X = $floorX & 0xFF; $Y = $floorY & 0xFF; $Z = $floorZ & 0xFF; $x -= $floorX; $y -= $floorY; $z -= $floorZ; //Fade curves //$fX = self::fade($x); //$fY = self::fade($y); //$fZ = self::fade($z); $fX = $x * $x * $x * ($x * ($x * 6 - 15) + 10); $fY = $y * $y * $y * ($y * ($y * 6 - 15) + 10); $fZ = $z * $z * $z * ($z * ($z * 6 - 15) + 10); //Cube corners $A = $this->perm[$X] + $Y; $B = $this->perm[$X + 1] + $Y; $AA = $this->perm[$A] + $Z; $AB = $this->perm[$A + 1] + $Z; $BA = $this->perm[$B] + $Z; $BB = $this->perm[$B + 1] + $Z; $AA1 = self::grad($this->perm[$AA], $x, $y, $z); $BA1 = self::grad($this->perm[$BA], $x - 1, $y, $z); $AB1 = self::grad($this->perm[$AB], $x, $y - 1, $z); $BB1 = self::grad($this->perm[$BB], $x - 1, $y - 1, $z); $AA2 = self::grad($this->perm[$AA + 1], $x, $y, $z - 1); $BA2 = self::grad($this->perm[$BA + 1], $x - 1, $y, $z - 1); $AB2 = self::grad($this->perm[$AB + 1], $x, $y - 1, $z - 1); $BB2 = self::grad($this->perm[$BB + 1], $x - 1, $y - 1, $z - 1); $xLerp11 = $AA1 + $fX * ($BA1 - $AA1); $zLerp1 = $xLerp11 + $fY * ($AB1 + $fX * ($BB1 - $AB1) - $xLerp11); $xLerp21 = $AA2 + $fX * ($BA2 - $AA2); return $zLerp1 + $fZ * ($xLerp21 + $fY * ($AB2 + $fX * ($BB2 - $AB2) - $xLerp21) - $zLerp1); /* return self::lerp( $fZ, self::lerp( $fY, self::lerp( $fX, self::grad($this->perm[$AA], $x, $y, $z), self::grad($this->perm[$BA], $x - 1, $y, $z) ), self::lerp( $fX, self::grad($this->perm[$AB], $x, $y - 1, $z), self::grad($this->perm[$BB], $x - 1, $y - 1, $z) ) ), self::lerp( $fY, self::lerp( $fX, self::grad($this->perm[$AA + 1], $x, $y, $z - 1), self::grad($this->perm[$BA + 1], $x - 1, $y, $z - 1) ), self::lerp( $fX, self::grad($this->perm[$AB + 1], $x, $y - 1, $z - 1), self::grad($this->perm[$BB + 1], $x - 1, $y - 1, $z - 1) ) ) ); */ } /** * @param $x * @param $y * * @return mixed */ public function getNoise2D($x, $y){ return $this->getNoise3D($x, $y, 0); } }offsetW = $random->nextFloat() * 256; self::$SQRT_3 = sqrt(3); self::$SQRT_5 = sqrt(5); self::$F2 = 0.5 * (self::$SQRT_3 - 1); self::$G2 = (3 - self::$SQRT_3) / 6; self::$G22 = self::$G2 * 2.0 - 1; self::$F3 = 1.0 / 3.0; self::$G3 = 1.0 / 6.0; self::$F4 = (self::$SQRT_5 - 1.0) / 4.0; self::$G4 = (5.0 - self::$SQRT_5) / 20.0; self::$G42 = self::$G4 * 2.0; self::$G43 = self::$G4 * 3.0; self::$G44 = self::$G4 * 4.0 - 1.0; } /** * @param $g * @param $x * @param $y * * @return mixed */ protected static function dot2D($g, $x, $y){ return $g[0] * $x + $g[1] * $y; } /** * @param $g * @param $x * @param $y * @param $z * * @return mixed */ protected static function dot3D($g, $x, $y, $z){ return $g[0] * $x + $g[1] * $y + $g[2] * $z; } /** * @param $g * @param $x * @param $y * @param $z * @param $w * * @return mixed */ protected static function dot4D($g, $x, $y, $z, $w){ return $g[0] * $x + $g[1] * $y + $g[2] * $z + $g[3] * $w; } /** * @param $x * @param $y * @param $z * * @return float */ public function getNoise3D($x, $y, $z){ $x += $this->offsetX; $y += $this->offsetY; $z += $this->offsetZ; // Skew the input space to determine which simplex cell we're in $s = ($x + $y + $z) * self::$F3; // Very nice and simple skew factor for 3D $i = (int) ($x + $s); $j = (int) ($y + $s); $k = (int) ($z + $s); $t = ($i + $j + $k) * self::$G3; // Unskew the cell origin back to (x,y,z) space $x0 = $x - ($i - $t); // The x,y,z distances from the cell origin $y0 = $y - ($j - $t); $z0 = $z - ($k - $t); // For the 3D case, the simplex shape is a slightly irregular tetrahedron. // Determine which simplex we are in. if($x0 >= $y0){ if($y0 >= $z0){ $i1 = 1; $j1 = 0; $k1 = 0; $i2 = 1; $j2 = 1; $k2 = 0; } // X Y Z order elseif($x0 >= $z0){ $i1 = 1; $j1 = 0; $k1 = 0; $i2 = 1; $j2 = 0; $k2 = 1; } // X Z Y order else{ $i1 = 0; $j1 = 0; $k1 = 1; $i2 = 1; $j2 = 0; $k2 = 1; } // Z X Y order }else{ // x0 0){ $gi0 = self::$grad3[$this->perm[$ii + $this->perm[$jj + $this->perm[$kk]]] % 12]; $n += $t0 * $t0 * $t0 * $t0 * ($gi0[0] * $x0 + $gi0[1] * $y0 + $gi0[2] * $z0); } $t1 = 0.6 - $x1 * $x1 - $y1 * $y1 - $z1 * $z1; if($t1 > 0){ $gi1 = self::$grad3[$this->perm[$ii + $i1 + $this->perm[$jj + $j1 + $this->perm[$kk + $k1]]] % 12]; $n += $t1 * $t1 * $t1 * $t1 * ($gi1[0] * $x1 + $gi1[1] * $y1 + $gi1[2] * $z1); } $t2 = 0.6 - $x2 * $x2 - $y2 * $y2 - $z2 * $z2; if($t2 > 0){ $gi2 = self::$grad3[$this->perm[$ii + $i2 + $this->perm[$jj + $j2 + $this->perm[$kk + $k2]]] % 12]; $n += $t2 * $t2 * $t2 * $t2 * ($gi2[0] * $x2 + $gi2[1] * $y2 + $gi2[2] * $z2); } $t3 = 0.6 - $x3 * $x3 - $y3 * $y3 - $z3 * $z3; if($t3 > 0){ $gi3 = self::$grad3[$this->perm[$ii + 1 + $this->perm[$jj + 1 + $this->perm[$kk + 1]]] % 12]; $n += $t3 * $t3 * $t3 * $t3 * ($gi3[0] * $x3 + $gi3[1] * $y3 + $gi3[2] * $z3); } // Add contributions from each corner to get the noise value. // The result is scaled to stay just inside [-1,1] return 32.0 * $n; } /** * @param $x * @param $y * * @return float */ public function getNoise2D($x, $y){ $x += $this->offsetX; $y += $this->offsetY; // Skew the input space to determine which simplex cell we're in $s = ($x + $y) * self::$F2; // Hairy factor for 2D $i = (int) ($x + $s); $j = (int) ($y + $s); $t = ($i + $j) * self::$G2; // Unskew the cell origin back to (x,y) space $x0 = $x - ($i - $t); // The x,y distances from the cell origin $y0 = $y - ($j - $t); // For the 2D case, the simplex shape is an equilateral triangle. // Determine which simplex we are in. if($x0 > $y0){ $i1 = 1; $j1 = 0; } // lower triangle, XY order: (0,0)->(1,0)->(1,1) else{ $i1 = 0; $j1 = 1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1) // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where // c = (3-sqrt(3))/6 $x1 = $x0 - $i1 + self::$G2; // Offsets for middle corner in (x,y) unskewed coords $y1 = $y0 - $j1 + self::$G2; $x2 = $x0 + self::$G22; // Offsets for last corner in (x,y) unskewed coords $y2 = $y0 + self::$G22; // Work out the hashed gradient indices of the three simplex corners $ii = $i & 255; $jj = $j & 255; $n = 0; // Calculate the contribution from the three corners $t0 = 0.5 - $x0 * $x0 - $y0 * $y0; if($t0 > 0){ $gi0 = self::$grad3[$this->perm[$ii + $this->perm[$jj]] % 12]; $n += $t0 * $t0 * $t0 * $t0 * ($gi0[0] * $x0 + $gi0[1] * $y0); // (x,y) of grad3 used for 2D gradient } $t1 = 0.5 - $x1 * $x1 - $y1 * $y1; if($t1 > 0){ $gi1 = self::$grad3[$this->perm[$ii + $i1 + $this->perm[$jj + $j1]] % 12]; $n += $t1 * $t1 * $t1 * $t1 * ($gi1[0] * $x1 + $gi1[1] * $y1); } $t2 = 0.5 - $x2 * $x2 - $y2 * $y2; if($t2 > 0){ $gi2 = self::$grad3[$this->perm[$ii + 1 + $this->perm[$jj + 1]] % 12]; $n += $t2 * $t2 * $t2 * $t2 * ($gi2[0] * $x2 + $gi2[1] * $y2); } // Add contributions from each corner to get the noise value. // The result is scaled to return values in the interval [-1,1]. return 70.0 * $n; } /** * Computes and returns the 4D simplex noise for the given coordinates in * 4D space * * @param float $x X coordinate * @param float $y Y coordinate * @param float $z Z coordinate * @param float $w W coordinate * * @return float Noise at given location, from range -1 to 1 */ /*public function getNoise4D($x, $y, $z, $w){ x += offsetX; y += offsetY; z += offsetZ; w += offsetW; n0, n1, n2, n3, n4; // Noise contributions from the five corners // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in s = (x + y + z + w) * self::$F4; // Factor for 4D skewing i = floor(x + s); j = floor(y + s); k = floor(z + s); l = floor(w + s); t = (i + j + k + l) * self::$G4; // Factor for 4D unskewing X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space Y0 = j - t; Z0 = k - t; W0 = l - t; x0 = x - X0; // The x,y,z,w distances from the cell origin y0 = y - Y0; z0 = z - Z0; w0 = w - W0; // For the 4D case, the simplex is a 4D shape I won't even try to describe. // To find out which of the 24 possible simplices we're in, we need to // determine the magnitude ordering of x0, y0, z0 and w0. // The method below is a good way of finding the ordering of x,y,z,w and // then find the correct traversal order for the simplex we’re in. // First, six pair-wise comparisons are performed between each possible pair // of the four coordinates, and the results are used to add up binary bits // for an integer index. c1 = (x0 > y0) ? 32 : 0; c2 = (x0 > z0) ? 16 : 0; c3 = (y0 > z0) ? 8 : 0; c4 = (x0 > w0) ? 4 : 0; c5 = (y0 > w0) ? 2 : 0; c6 = (z0 > w0) ? 1 : 0; c = c1 + c2 + c3 + c4 + c5 + c6; i1, j1, k1, l1; // The integer offsets for the second simplex corner i2, j2, k2, l2; // The integer offsets for the third simplex corner i3, j3, k3, l3; // The integer offsets for the fourth simplex corner // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; j1 = simplex[c][1] >= 3 ? 1 : 0; k1 = simplex[c][2] >= 3 ? 1 : 0; l1 = simplex[c][3] >= 3 ? 1 : 0; // The number 2 in the "simplex" array is at the second largest coordinate. i2 = simplex[c][0] >= 2 ? 1 : 0; j2 = simplex[c][1] >= 2 ? 1 : 0; k2 = simplex[c][2] >= 2 ? 1 : 0; l2 = simplex[c][3] >= 2 ? 1 : 0; // The number 1 in the "simplex" array is at the second smallest coordinate. i3 = simplex[c][0] >= 1 ? 1 : 0; j3 = simplex[c][1] >= 1 ? 1 : 0; k3 = simplex[c][2] >= 1 ? 1 : 0; l3 = simplex[c][3] >= 1 ? 1 : 0; // The fifth corner has all coordinate offsets = 1, so no need to look that up. x1 = x0 - i1 + self::$G4; // Offsets for second corner in (x,y,z,w) coords y1 = y0 - j1 + self::$G4; z1 = z0 - k1 + self::$G4; w1 = w0 - l1 + self::$G4; x2 = x0 - i2 + self::$G42; // Offsets for third corner in (x,y,z,w) coords y2 = y0 - j2 + self::$G42; z2 = z0 - k2 + self::$G42; w2 = w0 - l2 + self::$G42; x3 = x0 - i3 + self::$G43; // Offsets for fourth corner in (x,y,z,w) coords y3 = y0 - j3 + self::$G43; z3 = z0 - k3 + self::$G43; w3 = w0 - l3 + self::$G43; x4 = x0 + self::$G44; // Offsets for last corner in (x,y,z,w) coords y4 = y0 + self::$G44; z4 = z0 + self::$G44; w4 = w0 + self::$G44; // Work out the hashed gradient indices of the five simplex corners ii = i & 255; jj = j & 255; kk = k & 255; ll = l & 255; gi0 = $this->perm[ii + $this->perm[jj + $this->perm[kk + $this->perm[ll]]]] % 32; gi1 = $this->perm[ii + i1 + $this->perm[jj + j1 + $this->perm[kk + k1 + $this->perm[ll + l1]]]] % 32; gi2 = $this->perm[ii + i2 + $this->perm[jj + j2 + $this->perm[kk + k2 + $this->perm[ll + l2]]]] % 32; gi3 = $this->perm[ii + i3 + $this->perm[jj + j3 + $this->perm[kk + k3 + $this->perm[ll + l3]]]] % 32; gi4 = $this->perm[ii + 1 + $this->perm[jj + 1 + $this->perm[kk + 1 + $this->perm[ll + 1]]]] % 32; // Calculate the contribution from the five corners t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; if(t0 < 0){ n0 = 0.0; }else{ t0 *= t0; n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); } t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; if(t1 < 0){ n1 = 0.0; }else{ t1 *= t1; n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); } t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; if(t2 < 0){ n2 = 0.0; }else{ t2 *= t2; n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); } t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; if(t3 < 0){ n3 = 0.0; }else{ t3 *= t3; n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); } t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; if(t4 < 0){ n4 = 0.0; }else{ t4 *= t4; n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); } // Sum up and scale the result to cover the range [-1,1] return 27.0 * (n0 + n1 + n2 + n3 + n4); }*/ }waterHeight; } public function getSettings(){ return []; } public function pickBiome($x, $z){ $hash = $x * 2345803 ^ $z * 9236449 ^ $this->level->getSeed(); $hash *= $hash + 223; $xNoise = $hash >> 20 & 3; $zNoise = $hash >> 22 & 3; if($xNoise == 3){ $xNoise = 1; } if($zNoise == 3){ $zNoise = 1; } return $this->selector->pickBiome($x + $xNoise - 1, $z + $zNoise - 1); } public function init(ChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); $this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 32); $this->random->setSeed($this->level->getSeed()); $this->selector = new BiomeSelector($this->random, function($temperature, $rainfall){ if($rainfall < 0.25){ if($temperature < 0.7){ return Biome::OCEAN; }elseif($temperature < 0.85){ return Biome::RIVER; }else{ return Biome::SWAMP; } }elseif($rainfall < 0.60){ if($temperature < 0.25){ return Biome::ICE_PLAINS; }elseif($temperature < 0.75){ return Biome::PLAINS; }else{ return Biome::DESERT; } }elseif($rainfall < 0.80){ if($temperature < 0.25){ return Biome::TAIGA; }elseif($temperature < 0.75){ return Biome::FOREST; }else{ return Biome::BIRCH_FOREST; } }else{ if($temperature < 0.25){ return Biome::MOUNTAINS; }elseif($temperature < 0.70){ return Biome::SMALL_MOUNTAINS; }else{ return Biome::RIVER; } } }, Biome::getBiome(Biome::OCEAN)); $this->selector->addBiome(Biome::getBiome(Biome::OCEAN)); $this->selector->addBiome(Biome::getBiome(Biome::PLAINS)); $this->selector->addBiome(Biome::getBiome(Biome::DESERT)); $this->selector->addBiome(Biome::getBiome(Biome::MOUNTAINS)); $this->selector->addBiome(Biome::getBiome(Biome::FOREST)); $this->selector->addBiome(Biome::getBiome(Biome::TAIGA)); $this->selector->addBiome(Biome::getBiome(Biome::SWAMP)); $this->selector->addBiome(Biome::getBiome(Biome::RIVER)); $this->selector->addBiome(Biome::getBiome(Biome::ICE_PLAINS)); $this->selector->addBiome(Biome::getBiome(Biome::SMALL_MOUNTAINS)); $this->selector->addBiome(Biome::getBiome(Biome::BIRCH_FOREST)); $this->selector->addBiome(Biome::getBiome(Biome::BEACH)); $this->selector->addBiome(Biome::getBiome(Biome::MESA)); $this->selector->recalculate(); $cover = new GroundCover(); $this->generationPopulators[] = $cover; $cave = new Cave(); $this->populators[] = $cave; $ores = new Ore(); $ores->setOreTypes([ new OreType(new CoalOre(), 20, 16, 0, 128), new OreType(New IronOre(), 20, 8, 0, 64), new OreType(new RedstoneOre(), 8, 7, 0, 16), new OreType(new LapisOre(), 1, 6, 0, 32), new OreType(new GoldOre(), 2, 8, 0, 32), new OreType(new DiamondOre(), 1, 7, 0, 16), new OreType(new Dirt(), 20, 32, 0, 128), new OreType(new Stone(Stone::GRANITE), 20, 32, 0, 128), new OreType(new Stone(Stone::DIORITE), 20, 32, 0, 128), new OreType(new Stone(Stone::ANDESITE), 20, 32, 0, 128), new OreType(new Gravel(), 10, 16, 0, 128) ]); $this->populators[] = $ores; } public function generateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); $noise = Generator::getFastNoise3D($this->noiseBase, 16, 128, 16, 4, 8, 4, $chunkX * 16, 0, $chunkZ * 16); $chunk = $this->level->getChunk($chunkX, $chunkZ); $biomeCache = []; for($x = 0; $x < 16; ++$x){ for($z = 0; $z < 16; ++$z){ $minSum = 0; $maxSum = 0; $weightSum = 0; $biome = $this->pickBiome($chunkX * 16 + $x, $chunkZ * 16 + $z); $chunk->setBiomeId($x, $z, $biome->getId()); for($sx = -self::$SMOOTH_SIZE; $sx <= self::$SMOOTH_SIZE; ++$sx){ for($sz = -self::$SMOOTH_SIZE; $sz <= self::$SMOOTH_SIZE; ++$sz){ $weight = self::$GAUSSIAN_KERNEL[$sx + self::$SMOOTH_SIZE][$sz + self::$SMOOTH_SIZE]; if($sx === 0 and $sz === 0){ $adjacent = $biome; }else{ $index = Level::chunkHash($chunkX * 16 + $x + $sx, $chunkZ * 16 + $z + $sz); if(isset($biomeCache[$index])){ $adjacent = $biomeCache[$index]; }else{ $biomeCache[$index] = $adjacent = $this->pickBiome($chunkX * 16 + $x + $sx, $chunkZ * 16 + $z + $sz); } } $minSum += ($adjacent->getMinElevation() - 1) * $weight; $maxSum += $adjacent->getMaxElevation() * $weight; $weightSum += $weight; } } $minSum /= $weightSum; $maxSum /= $weightSum; $solidLand = false; for($y = 127; $y >= 0; --$y){ if($y === 0){ $chunk->setBlockId($x, $y, $z, Block::BEDROCK); continue; } // A noiseAdjustment of 1 will guarantee ground, a noiseAdjustment of -1 will guarantee air. //$effHeight = min($y - $smoothHeight - $minSum, $noiseAdjustment = 2 * (($maxSum - $y) / ($maxSum - $minSum)) - 1; // To generate caves, we bring the noiseAdjustment down away from 1. $caveLevel = $minSum - 10; $distAboveCaveLevel = max(0, $y - $caveLevel); // must be positive $noiseAdjustment = min($noiseAdjustment, 0.4 + ($distAboveCaveLevel / 10)); $noiseValue = $noise[$x][$z][$y] + $noiseAdjustment; if($noiseValue > 0){ $chunk->setBlockId($x, $y, $z, Block::STONE); $solidLand = true; }elseif($y <= $this->waterHeight && $solidLand == false){ $chunk->setBlockId($x, $y, $z, Block::STILL_WATER); } } } } foreach($this->generationPopulators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } } public function populateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); foreach($this->populators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } $chunk = $this->level->getChunk($chunkX, $chunkZ); $biome = Biome::getBiome($chunk->getBiomeId(7, 7)); $biome->populateChunk($this->level, $chunkX, $chunkZ, $this->random); } public function getSpawn(){ return new Vector3(127.5, 128, 127.5); } }level->getSeed(); $hash *= $hash + 223; $xNoise = $hash >> 20 & 3; $zNoise = $hash >> 22 & 3; if($xNoise == 3){ $xNoise = 1; } if($zNoise == 3){ $zNoise = 1; } return $this->selector->pickBiome($x + $xNoise - 1, $z + $zNoise - 1); } public function init(ChunkManager $level, Random $random){ $this->level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); $this->noiseSeaFloor = new Simplex($this->random, 1, 1 / 8, 1 / 64); $this->noiseLand = new Simplex($this->random, 2, 1 / 8, 1 / 512); $this->noiseMountains = new Simplex($this->random, 4, 1, 1 / 500); $this->noiseBaseGround = new Simplex($this->random, 4, 1 / 4, 1 / 64); $this->noiseRiver = new Simplex($this->random, 2, 1, 1 / 512); $this->random->setSeed($this->level->getSeed()); $this->selector = new BiomeSelector($this->random, function($temperature, $rainfall){ if($rainfall < 0.25){ if($temperature < 0.7){ return Biome::OCEAN; }elseif($temperature < 0.85){ return Biome::RIVER; }else{ return Biome::SWAMP; } }elseif($rainfall < 0.60){ if($temperature < 0.25){ return Biome::ICE_PLAINS; }elseif($temperature < 0.75){ return Biome::PLAINS; }else{ return Biome::DESERT; } }elseif($rainfall < 0.80){ if($temperature < 0.25){ return Biome::TAIGA; }elseif($temperature < 0.75){ return Biome::FOREST; }else{ return Biome::BIRCH_FOREST; } }else{ if($temperature < 0.25){ return Biome::MOUNTAINS; }elseif($temperature < 0.70){ return Biome::SMALL_MOUNTAINS; }else{ return Biome::RIVER; } } }, Biome::getBiome(Biome::OCEAN)); $this->heightOffset = $random->nextRange(-5, 3); $this->selector->addBiome(Biome::getBiome(Biome::OCEAN)); $this->selector->addBiome(Biome::getBiome(Biome::PLAINS)); $this->selector->addBiome(Biome::getBiome(Biome::DESERT)); $this->selector->addBiome(Biome::getBiome(Biome::MOUNTAINS)); $this->selector->addBiome(Biome::getBiome(Biome::FOREST)); $this->selector->addBiome(Biome::getBiome(Biome::TAIGA)); $this->selector->addBiome(Biome::getBiome(Biome::SWAMP)); $this->selector->addBiome(Biome::getBiome(Biome::RIVER)); $this->selector->addBiome(Biome::getBiome(Biome::ICE_PLAINS)); $this->selector->addBiome(Biome::getBiome(Biome::SMALL_MOUNTAINS)); $this->selector->addBiome(Biome::getBiome(Biome::BIRCH_FOREST)); $this->selector->addBiome(Biome::getBiome(Biome::BEACH)); $this->selector->addBiome(Biome::getBiome(Biome::MESA)); $this->selector->recalculate(); $cover = new GroundCover(); $this->generationPopulators[] = $cover; $cave = new Cave(); $this->populators[] = $cave; $ores = new Ore(); $ores->setOreTypes([ new OreType(new CoalOre(), 20, 17, 0, 128), new OreType(new IronOre(), 20, 9, 0, 64), new OreType(new RedstoneOre(), 8, 8, 0, 16), new OreType(new LapisOre(), 1, 7, 0, 16), new OreType(new GoldOre(), 2, 9, 0, 32), new OreType(new DiamondOre(), 1, 8, 0, 16), new OreType(new Dirt(), 10, 33, 0, 128), new OreType(new Gravel(), 8, 33, 0, 128), new OreType(new Stone(Stone::GRANITE), 10, 33, 0, 80), new OreType(new Stone(Stone::DIORITE), 10, 33, 0, 80), new OreType(new Stone(Stone::ANDESITE), 10, 33, 0, 80) ]); $this->populators[] = $ores; } public function generateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); $seaFloorNoise = Generator::getFastNoise2D($this->noiseSeaFloor, 16, 16, 4, $chunkX * 16, 0, $chunkZ * 16); $landNoise = Generator::getFastNoise2D($this->noiseLand, 16, 16, 4, $chunkX * 16, 0, $chunkZ * 16); $mountainNoise = Generator::getFastNoise2D($this->noiseMountains, 16, 16, 4, $chunkX * 16, 0, $chunkZ * 16); $baseNoise = Generator::getFastNoise2D($this->noiseBaseGround, 16, 16, 4, $chunkX * 16, 0, $chunkZ * 16); $riverNoise = Generator::getFastNoise2D($this->noiseRiver, 16, 16, 4, $chunkX * 16, 0, $chunkZ * 16); $chunk = $this->level->getChunk($chunkX, $chunkZ); for($genx = 0; $genx < 16; $genx++){ for($genz = 0; $genz < 16; $genz++){ $canBaseGround = false; $canRiver = true; //using a quadratic function which smooth the world //y = (2.956x)^2 - 0.6, (0 <= x <= 2) $landHeightNoise = $landNoise[$genx][$genz] + 1; $landHeightNoise *= 2.956; $landHeightNoise = $landHeightNoise * $landHeightNoise; $landHeightNoise = $landHeightNoise - 0.6; $landHeightNoise = $landHeightNoise > 0 ? $landHeightNoise : 0; //generate mountains $mountainHeightGenerate = $mountainNoise[$genx][$genz] - 0.2; $mountainHeightGenerate = $mountainHeightGenerate > 0 ? $mountainHeightGenerate : 0; $mountainGenerate = (int) ($this->mountainHeight * $mountainHeightGenerate); $landHeightGenerate = (int) ($this->landHeightRange * $landHeightNoise); if($landHeightGenerate > $this->landHeightRange){ if($landHeightGenerate > $this->landHeightRange){ $canBaseGround = true; } $landHeightGenerate = $this->landHeightRange; } $genyHeight = $this->seaFloorHeight + $landHeightGenerate; $genyHeight += $mountainGenerate; //prepare for generate ocean, desert, and land if($genyHeight < $this->beathStartHeight){ if($genyHeight < $this->beathStartHeight - 5){ $genyHeight += (int) ($this->seaFloorGenerateRange * $seaFloorNoise[$genx][$genz]); } $biome = Biome::getBiome(Biome::OCEAN); if($genyHeight < $this->seaFloorHeight - $this->seaFloorGenerateRange){ $genyHeight = $this->seaFloorHeight; } $canRiver = false; }else if($genyHeight <= $this->beathStopHeight && $genyHeight >= $this->beathStartHeight){ $biome = Biome::getBiome(Biome::BEACH); }else{ $biome = $this->pickBiome($chunkX * 16 + $genx, $chunkZ * 16 + $genz); if($canBaseGround){ $baseGroundHeight = (int) ($this->landHeightRange * $landHeightNoise) - $this->landHeightRange; $baseGroundHeight2 = (int) ($this->basegroundHeight * ($baseNoise[$genx][$genz] + 1)); if($baseGroundHeight2 > $baseGroundHeight) $baseGroundHeight2 = $baseGroundHeight; if($baseGroundHeight2 > $mountainGenerate) $baseGroundHeight2 = $baseGroundHeight2 - $mountainGenerate; else $baseGroundHeight2 = 0; $genyHeight += $baseGroundHeight2; } } if($canRiver && $genyHeight <= $this->seaHeight - 5){ $canRiver = false; } //generate river if($canRiver){ $riverGenerate = $riverNoise[$genx][$genz]; if($riverGenerate > -0.25 && $riverGenerate < 0.25){ $riverGenerate = $riverGenerate > 0 ? $riverGenerate : -$riverGenerate; $riverGenerate = 0.25 - $riverGenerate; //y=x^2 * 4 - 0.0000001 $riverGenerate = $riverGenerate * $riverGenerate * 4; //smooth again $riverGenerate = $riverGenerate - 0.0000001; $riverGenerate = $riverGenerate > 0 ? $riverGenerate : 0; $genyHeight -= $riverGenerate * 64; if($genyHeight < $this->seaHeight){ $biome = Biome::getBiome(Biome::RIVER); //to generate river floor if($genyHeight <= $this->seaHeight - 8){ $genyHeight1 = $this->seaHeight - 9 + (int) ($this->basegroundHeight * ($baseNoise[$genx][$genz] + 1)); $genyHeight2 = $genyHeight < $this->seaHeight - 7 ? $this->seaHeight - 7 : $genyHeight; $genyHeight = $genyHeight1 > $genyHeight2 ? $genyHeight1 : $genyHeight2; } } } } $chunk->setBiomeId($genx, $genz, $biome->getId()); //generating $generateHeight = $genyHeight > $this->seaHeight ? $genyHeight : $this->seaHeight; for($geny = 0; $geny <= $generateHeight; $geny++){ if($geny <= $this->bedrockDepth && ($geny == 0 or $this->random->nextRange(1, 5) == 1)){ $chunk->setBlockId($genx, $geny, $genz, Block::BEDROCK); }elseif($geny > $genyHeight){ if(($biome->getId() == Biome::ICE_PLAINS or $biome->getId() == Biome::TAIGA) and $geny == $this->seaHeight){ $chunk->setBlockId($genx, $geny, $genz, Block::ICE); }else{ $chunk->setBlockId($genx, $geny, $genz, Block::STILL_WATER); } }else{ $chunk->setBlockId($genx, $geny, $genz, Block::STONE); } } } } //populator chunk foreach($this->generationPopulators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } } public function populateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); foreach($this->populators as $populator){ $populator->populate($this->level, $chunkX, $chunkZ, $this->random); } $chunk = $this->level->getChunk($chunkX, $chunkZ); $biome = Biome::getBiome($chunk->getBiomeId(7, 7)); $biome->populateChunk($this->level, $chunkX, $chunkZ, $this->random); } public function getSpawn(){ return new Vector3(127.5, 128, 127.5); } } removePopulator(Cactus::class); $this->removePopulator(DeadBush::class); $this->setElevation(62, 65); } /** * @return string */ public function getName() : string{ return "Beach"; } } setElevation(63, 74); $this->temperature = 2; $this->rainfall = 0; } /** * @return string */ public function getName() : string{ return "Desert"; } }type = $type; $trees = new Tree($type === self::TYPE_BIRCH ? Sapling::BIRCH : Sapling::OAK); $trees->setBaseAmount(5); $this->addPopulator($trees); $tallGrass = new TallGrass(); $tallGrass->setBaseAmount(3); $this->addPopulator($tallGrass); $this->setElevation(63, 81); if($type === self::TYPE_BIRCH){ $this->temperature = 0.6; $this->rainfall = 0.5; }else{ $this->temperature = 0.7; $this->rainfall = 0.8; } } /** * @return string */ public function getName() : string{ return $this->type === self::TYPE_BIRCH ? "Birch Forest" : "Forest"; } }setGroundCover([ Block::get(Block::GRASS, 0), Block::get(Block::DIRT, 0), Block::get(Block::DIRT, 0), Block::get(Block::DIRT, 0), Block::get(Block::DIRT, 0), ]); } }setBaseAmount(5); $this->addPopulator($tallGrass); $this->setElevation(63, 74); $this->temperature = 0.05; $this->rainfall = 0.8; } /** * @return string */ public function getName() : string{ return "Ice Plains"; } }setBaseAmount(0); $cactus->setRandomAmount(5); $deadBush = new DeadBush(); $cactus->setBaseAmount(2); $deadBush->setRandomAmount(10); $this->addPopulator($cactus); $this->addPopulator($deadBush); $this->setElevation(63, 81); $this->temperature = 2.0; $this->rainfall = 0.8; $this->setGroundCover([ Block::get(Block::HARDENED_CLAY, 0), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_PINK), Block::get(Block::HARDENED_CLAY, 0), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_ORANGE), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_BLACK), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_GRAY), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_WHITE), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_ORANGE), Block::get(Block::HARDENED_CLAY, 0), Block::get(Block::HARDENED_CLAY, 0), Block::get(Block::HARDENED_CLAY, 0), Block::get(Block::HARDENED_CLAY, 0), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_YELLOW), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_BLACK), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_PINK), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_PINK), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::STAINED_CLAY, StainedClay::CLAY_WHITE), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::RED_SANDSTONE, 0), Block::get(Block::RED_SANDSTONE, 0), ]); } /** * @return string */ public function getName() : string{ return "Mesa"; } } setBaseAmount(1); $this->addPopulator($trees); $tallGrass = new TallGrass(); $tallGrass->setBaseAmount(6); $this->addPopulator($tallGrass); //TODO: add emerald $this->setElevation(63, 127); $this->temperature = 0.4; $this->rainfall = 0.5; } /** * @return string */ public function getName() : string{ return "Mountains"; } }setBaseAmount(6); $tallGrass = new TallGrass(); $tallGrass->setBaseAmount(5); $this->addPopulator($sugarcane); $this->addPopulator($tallGrass); $this->setElevation(46, 68); $this->temperature = 0.5; $this->rainfall = 0.5; } /** * @return string */ public function getName() : string{ return "Ocean"; } } setBaseAmount(6); $tallGrass = new TallGrass(); $tallGrass->setBaseAmount(25); $waterPit = new WaterPit(); $waterPit->setBaseAmount(9999); $lilyPad = new LilyPad(); $lilyPad->setBaseAmount(8); $flower = new Flower(); $flower->setBaseAmount(2); $flower->addType([Block::DANDELION, 0]); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_POPPY]); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_AZURE_BLUET]); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_RED_TULIP]); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_ORANGE_TULIP]); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_WHITE_TULIP]); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_PINK_TULIP]); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_OXEYE_DAISY]); $this->addPopulator($sugarcane); $this->addPopulator($tallGrass); $this->addPopulator($flower); $this->addPopulator($waterPit); $this->addPopulator($lilyPad); $this->setElevation(61, 68); $this->temperature = 0.8; $this->rainfall = 0.4; } /** * @return string */ public function getName() : string{ return "Plains"; } } setBaseAmount(6); $tallGrass = new TallGrass(); $tallGrass->setBaseAmount(5); $this->addPopulator($sugarcane); $this->addPopulator($tallGrass); $this->setElevation(58, 62); $this->temperature = 0.5; $this->rainfall = 0.7; } /** * @return string */ public function getName() : string{ return "River"; } } setBaseAmount(6); $deadBush = new DeadBush(); $deadBush->setBaseAmount(2); $this->addPopulator($cactus); $this->addPopulator($deadBush); $this->setElevation(63, 81); $this->temperature = 0.05; $this->rainfall = 0.8; $this->setGroundCover([ Block::get(Block::SAND, 0), Block::get(Block::SAND, 0), Block::get(Block::SAND, 0), Block::get(Block::SAND, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), Block::get(Block::SANDSTONE, 0), ]); } /** * @return string */ public function getName() : string{ return "Sandy"; } } setElevation(63, 97); } /** * @return string */ public function getName() : string{ return "Small Mountains"; } }setGroundCover([ Block::get(Block::SNOW_LAYER, 0), Block::get(Block::GRASS, 0), Block::get(Block::DIRT, 0), Block::get(Block::DIRT, 0), Block::get(Block::DIRT, 0), ]); } }setBaseAmount(8); $flower->addType([Block::RED_FLOWER, FlowerBlock::TYPE_BLUE_ORCHID]); $this->addPopulator($flower); $lilypad = new LilyPad(); $lilypad->setBaseAmount(4); $this->addPopulator($lilypad); $this->setElevation(62, 63); $this->temperature = 0.8; $this->rainfall = 0.9; } /** * @return string */ public function getName() : string{ return "Swamp"; } }setBaseAmount(10); $this->addPopulator($trees); $mossStone = new MossStone(); $mossStone->setBaseAmount(1); $this->addPopulator($mossStone); $this->setElevation(63, 81); $this->temperature = 0.05; $this->rainfall = 0.8; $this->setGroundCover([ Block::get(Block::PODZOL, 0), Block::get(Block::DIRT, 0), Block::get(Block::DIRT, 0), Block::get(Block::DIRT, 0) ]); } /** * @return string */ public function getName() : string{ return "Taiga"; } } trunkBlock = Block::WOOD2; $this->leafBlock = Block::LEAVES2; $this->leafType = Leaves2::ACACIA; $this->type = Wood2::ACACIA; $this->treeHeight = 8; } /*public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ }*/ //TODO: rewrite } true, Block::LEAVES => true, Block::SAPLING => true ]; /** @var Random */ private $random; private $trunkHeightMultiplier = 0.618; private $trunkHeight; private $leafAmount = 1; private $leafDistanceLimit = 5; private $widthScale = 1; private $branchSlope = 0.381; private $totalHeight; private $baseHeight = 5; /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random * * @return bool */ public function canPlaceObject(ChunkManager $level, $x, $y, $z, Random $random){ if(!parent::canPlaceObject($level, $x, $y, $z, $random) or $level->getBlockIdAt($x, $y, $z) == Block::WATER or $level->getBlockIdAt($x, $y, $z) == Block::STILL_WATER){ return false; } $base = new Vector3($x, $y, $z); $this->totalHeight = $this->baseHeight + $random->nextBoundedInt(12); $availableSpace = $this->getAvailableBlockSpace($level, $base, $base->add(0, $this->totalHeight - 1, 0)); if($availableSpace > $this->baseHeight or $availableSpace == -1){ if($availableSpace != -1){ $this->totalHeight = $availableSpace; } return true; } return false; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random */ public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ $this->random = $random; $this->trunkHeight = (int) ($this->totalHeight * $this->trunkHeightMultiplier); $leaves = $this->getLeafGroupPoints($level, $x, $y, $z); foreach($leaves as $leaf){ /** @var Vector3 $leafGroup */ $leafGroup = $leaf[0]; $groupX = $leafGroup->getX(); $groupY = $leafGroup->getY(); $groupZ = $leafGroup->getZ(); for($yy = $groupY; $yy < $groupY + $this->leafDistanceLimit; ++$yy){ $this->generateGroupLayer($level, $groupX, $yy, $groupZ, $this->getLeafGroupLayerSize($yy - $groupY)); } } $trunk = new VectorIterator($level, new Vector3($x, $y - 1, $z), new Vector3($x, $y + $this->trunkHeight, $z)); while($trunk->valid()){ $trunk->next(); $pos = $trunk->current(); $level->setBlockIdAt($pos->x, $pos->y, $pos->z, Block::LOG); } $this->generateBranches($level, $x, $y, $z, $leaves); } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * * @return array */ private function getLeafGroupPoints(ChunkManager $level, $x, $y, $z){ $amount = $this->leafAmount * $this->totalHeight / 13; $groupsPerLayer = (int) (1.382 + $amount * $amount); if($groupsPerLayer == 0){ $groupsPerLayer = 1; } $trunkTopY = $y + $this->trunkHeight; $groups = []; $groupY = $y + $this->totalHeight - $this->leafDistanceLimit; $groups[] = [new Vector3($x, $groupY, $z), $trunkTopY]; for($currentLayer = (int) ($this->totalHeight - $this->leafDistanceLimit); $currentLayer >= 0; $currentLayer--){ $layerSize = $this->getRoughLayerSize($currentLayer); if($layerSize < 0){ $groupY--; continue; } for($count = 0; $count < $groupsPerLayer; $count++){ $scale = $this->widthScale * $layerSize * ($this->random->nextFloat() + 0.328); $randomOffset = Vector2::createRandomDirection($this->random)->multiply($scale); $groupX = (int) ($randomOffset->getX() + $x + 0.5); $groupZ = (int) ($randomOffset->getY() + $z + 0.5); $group = new Vector3($groupX, $groupY, $groupZ); if($this->getAvailableBlockSpace($level, $group, $group->add(0, $this->leafDistanceLimit, 0)) != -1){ continue; } $xOff = (int) ($x - $groupX); $zOff = (int) ($z - $groupZ); $horizontalDistanceToTrunk = sqrt($xOff * $xOff + $zOff * $zOff); $verticalDistanceToTrunk = $horizontalDistanceToTrunk * $this->branchSlope; $yDiff = (int) ($groupY - $verticalDistanceToTrunk); if($yDiff > $trunkTopY){ $base = $trunkTopY; }else{ $base = $yDiff; } if($this->getAvailableBlockSpace($level, new Vector3($x, $base, $z), $group) == -1){ $groups[] = [$group, $base]; } } $groupY--; } return $groups; } /** * @param int $y * * @return int */ private function getLeafGroupLayerSize(int $y){ if($y >= 0 and $y < $this->leafDistanceLimit){ return (int) (($y != ($this->leafDistanceLimit - 1)) ? 3 : 2); } return -1; } /** * @param ChunkManager $level * @param int $x * @param int $y * @param int $z * @param int $size */ private function generateGroupLayer(ChunkManager $level, int $x, int $y, int $z, int $size){ for($xx = $x - $size; $xx <= $x + $size; $xx++){ for($zz = $z - $size; $zz <= $z + $size; $zz++){ $sizeX = abs($x - $xx) + 0.5; $sizeZ = abs($z - $zz) + 0.5; if(($sizeX * $sizeX + $sizeZ * $sizeZ) <= ($size * $size)){ if(isset($this->overridable[$level->getBlockIdAt($xx, $y, $zz)])){ $level->setBlockIdAt($xx, $y, $zz, Block::LEAVES); } } } } } /** * @param int $layer * * @return float */ private function getRoughLayerSize(int $layer) : float{ $halfHeight = $this->totalHeight / 2; if($layer < ($this->totalHeight / 3)){ return -1; }elseif($layer == $halfHeight){ return $halfHeight / 4; }elseif($layer >= $this->totalHeight or $layer <= 0){ return 0; }else{ return sqrt($halfHeight * $halfHeight - ($layer - $halfHeight) * ($layer - $halfHeight)) / 2; } } /** * @param ChunkManager $level * @param int $x * @param int $y * @param int $z * @param array $groups */ private function generateBranches(ChunkManager $level, int $x, int $y, int $z, array $groups){ foreach($groups as $group){ $baseY = $group[1]; if(($baseY - $y) >= ($this->totalHeight * 0.2)){ $base = new Vector3($x, $baseY, $z); $branch = new VectorIterator($level, $base, $group[0]); while($branch->valid()){ $branch->next(); $pos = $branch->current(); $level->setBlockIdAt((int) $pos->x, (int) $pos->y, (int) $pos->z, Block::LOG); $level->updateBlockLight((int) $pos->x, (int) $pos->y, (int) $pos->z); } } } } /** * @param ChunkManager $level * @param Vector3 $from * @param Vector3 $to * * @return int */ private function getAvailableBlockSpace(ChunkManager $level, Vector3 $from, Vector3 $to){ $count = 0; $iter = new VectorIterator($level, $from, $to); while($iter->valid()){ $iter->next(); $pos = $iter->current(); if(!isset($this->overridable[$level->getBlockIdAt($pos->x, $pos->y, $pos->z)])){ return $count; } $count++; } return -1; } }trunkBlock = Block::LOG; $this->leafBlock = Block::LEAVES; $this->leafType = Leaves::BIRCH; $this->type = Wood::BIRCH; $this->superBirch = (bool) $superBirch; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random */ public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ $this->treeHeight = $random->nextBoundedInt(3) + 5; if($this->superBirch){ $this->treeHeight += 5; } parent::placeObject($level, $x, $y, $z, $random); } }trunkBlock = Block::WOOD2; $this->leafBlock = Block::LEAVES2; $this->leafType = Leaves2::DARK_OAK; $this->type = Wood2::DARK_OAK; $this->treeHeight = 8; } }trunkBlock = Block::LOG; $this->leafBlock = Block::LEAVES; $this->leafType = Leaves::JUNGLE; $this->type = Wood::JUNGLE; $this->treeHeight = 8; } }type = $type; $this->random = $random; } /** * @return OreType */ public function getType(){ return $this->type; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * * @return bool */ public function canPlaceObject(ChunkManager $level, $x, $y, $z){ return ($level->getBlockIdAt($x, $y, $z) === 87); } /** * @param ChunkManager $level * @param $x * @param $y * @param $z */ public function placeObject(ChunkManager $level, $x, $y, $z){ $clusterSize = (int) $this->type->clusterSize; $angle = $this->random->nextFloat() * M_PI; $offset = VectorMath::getDirection2D($angle)->multiply($clusterSize)->divide(8); $x1 = $x + 8 + $offset->x; $x2 = $x + 8 - $offset->x; $z1 = $z + 8 + $offset->y; $z2 = $z + 8 - $offset->y; $y1 = $y + $this->random->nextBoundedInt(3) + 2; $y2 = $y + $this->random->nextBoundedInt(3) + 2; for($count = 0; $count <= $clusterSize; ++$count){ $seedX = $x1 + ($x2 - $x1) * $count / $clusterSize; $seedY = $y1 + ($y2 - $y1) * $count / $clusterSize; $seedZ = $z1 + ($z2 - $z1) * $count / $clusterSize; $size = ((sin($count * (M_PI / $clusterSize)) + 1) * $this->random->nextFloat() * $clusterSize / 16 + 1) / 2; $startX = (int) ($seedX - $size); $startY = (int) ($seedY - $size); $startZ = (int) ($seedZ - $size); $endX = (int) ($seedX + $size); $endY = (int) ($seedY + $size); $endZ = (int) ($seedZ + $size); //echo "ORE: $startX, $startY, $startZ,, $endX, $endY, $endZ\n"; for($x = $startX; $x <= $endX; ++$x){ $sizeX = ($x + 0.5 - $seedX) / $size; $sizeX *= $sizeX; if($sizeX < 1){ for($y = $startY; $y <= $endY; ++$y){ $sizeY = ($y + 0.5 - $seedY) / $size; $sizeY *= $sizeY; if($y > 0 and ($sizeX + $sizeY) < 1){ for($z = $startZ; $z <= $endZ; ++$z){ $sizeZ = ($z + 0.5 - $seedZ) / $size; $sizeZ *= $sizeZ; if(($sizeX + $sizeY + $sizeZ) < 1 and $level->getBlockIdAt($x, $y, $z) === 87){ $level->setBlockIdAt($x, $y, $z, $this->type->material->getId()); if($this->type->material->getDamage() !== 0){ $level->setBlockDataAt($x, $y, $z, $this->type->material->getDamage()); } //echo "Placed to $x, $y, $z\n"; } } } } } } } } }type = $type; $this->random = $random; } /** * @return OreType */ public function getType(){ return $this->type; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * * @return bool */ public function canPlaceObject(ChunkManager $level, $x, $y, $z){ return ($level->getBlockIdAt($x, $y, $z) === 0); } /** * @param ChunkManager $level * @param $x * @param $y * @param $z */ public function placeObject(ChunkManager $level, $x, $y, $z){ $clusterSize = (int) $this->type->clusterSize; $angle = $this->random->nextFloat() * M_PI; $offset = VectorMath::getDirection2D($angle)->multiply($clusterSize)->divide(8); $x1 = $x + 8 + $offset->x; $x2 = $x + 8 - $offset->x; $z1 = $z + 8 + $offset->y; $z2 = $z + 8 - $offset->y; $y1 = $y + $this->random->nextBoundedInt(3) + 2; $y2 = $y + $this->random->nextBoundedInt(3) + 2; for($count = 0; $count <= $clusterSize; ++$count){ $seedX = $x1 + ($x2 - $x1) * $count / $clusterSize; $seedY = $y1 + ($y2 - $y1) * $count / $clusterSize; $seedZ = $z1 + ($z2 - $z1) * $count / $clusterSize; $size = ((sin($count * (M_PI / $clusterSize)) + 1) * $this->random->nextFloat() * $clusterSize / 16 + 1) / 2; $startX = (int) ($seedX - $size); $startY = (int) ($seedY - $size); $startZ = (int) ($seedZ - $size); $endX = (int) ($seedX + $size); $endY = (int) ($seedY + $size); $endZ = (int) ($seedZ + $size); //echo "ORE: $startX, $startY, $startZ,, $endX, $endY, $endZ\n"; for($x = $startX; $x <= $endX; ++$x){ $sizeX = ($x + 0.5 - $seedX) / $size; $sizeX *= $sizeX; if($sizeX < 1){ for($y = $startY; $y <= $endY; ++$y){ $sizeY = ($y + 0.5 - $seedY) / $size; $sizeY *= $sizeY; if($y > 0 and ($sizeX + $sizeY) < 1){ for($z = $startZ; $z <= $endZ; ++$z){ $sizeZ = ($z + 0.5 - $seedZ) / $size; $sizeZ *= $sizeZ; if(($sizeX + $sizeY + $sizeZ) < 1 and $level->getBlockIdAt($x, $y, $z) === 0){ $level->setBlockIdAt($x, $y, $z, $this->type->material->getId()); if($this->type->material->getDamage() !== 0){ $level->setBlockDataAt($x, $y, $z, $this->type->material->getDamage()); } $level->updateBlockLight($x, $y, $z); //echo "Placed to $x, $y, $z\n"; } } } } } } } } }trunkBlock = Block::LOG; $this->leafBlock = Block::LEAVES; $this->leafType = Leaves::OAK; $this->type = Wood::OAK; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random */ public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ $this->treeHeight = $random->nextBoundedInt(3) + 4; parent::placeObject($level, $x, $y, $z, $random); } }type = $type; $this->random = $random; } /** * @return OreType */ public function getType(){ return $this->type; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * * @return bool */ public function canPlaceObject(ChunkManager $level, $x, $y, $z){ return (($level->getBlockIdAt($x, $y, $z) === 1) or ($level->getBlockIdAt($x, $y, $z) === 87)); } /** * @param ChunkManager $level * @param $x * @param $y * @param $z */ public function placeObject(ChunkManager $level, $x, $y, $z){ $clusterSize = (int) $this->type->clusterSize; $angle = $this->random->nextFloat() * M_PI; $offset = VectorMath::getDirection2D($angle)->multiply($clusterSize)->divide(8); $x1 = $x + 8 + $offset->x; $x2 = $x + 8 - $offset->x; $z1 = $z + 8 + $offset->y; $z2 = $z + 8 - $offset->y; $y1 = $y + $this->random->nextBoundedInt(3) + 2; $y2 = $y + $this->random->nextBoundedInt(3) + 2; for($count = 0; $count <= $clusterSize; ++$count){ $seedX = $x1 + ($x2 - $x1) * $count / $clusterSize; $seedY = $y1 + ($y2 - $y1) * $count / $clusterSize; $seedZ = $z1 + ($z2 - $z1) * $count / $clusterSize; $size = ((sin($count * (M_PI / $clusterSize)) + 1) * $this->random->nextFloat() * $clusterSize / 16 + 1) / 2; $startX = (int) ($seedX - $size); $startY = (int) ($seedY - $size); $startZ = (int) ($seedZ - $size); $endX = (int) ($seedX + $size); $endY = (int) ($seedY + $size); $endZ = (int) ($seedZ + $size); for($x = $startX; $x <= $endX; ++$x){ $sizeX = ($x + 0.5 - $seedX) / $size; $sizeX *= $sizeX; if($sizeX < 1){ for($y = $startY; $y <= $endY; ++$y){ $sizeY = ($y + 0.5 - $seedY) / $size; $sizeY *= $sizeY; if($y > 0 and ($sizeX + $sizeY) < 1){ for($z = $startZ; $z <= $endZ; ++$z){ $sizeZ = ($z + 0.5 - $seedZ) / $size; $sizeZ *= $sizeZ; if(($sizeX + $sizeY + $sizeZ) < 1 and (($level->getBlockIdAt($x, $y, $z) === 1) or ($level->getBlockIdAt($x, $y, $z) === 87))){ $level->setBlockIdAt($x, $y, $z, $this->type->material->getId()); if($this->type->material->getDamage() !== 0){ $level->setBlockDataAt($x, $y, $z, $this->type->material->getDamage()); } } } } } } } } } }material = $material; $this->clusterCount = (int) $clusterCount; $this->clusterSize = (int) $clusterSize; $this->maxHeight = (int) $maxHeight; $this->minHeight = (int) $minHeight; } }type = $type; $this->random = $random; } /** * @param ChunkManager $level * @param Vector3 $pos */ public function canPlaceObject(ChunkManager $level, Vector3 $pos){ } /** * @param ChunkManager $level * @param Vector3 $pos */ public function placeObject(ChunkManager $level, Vector3 $pos){ } }trunkBlock = Block::LOG; $this->leafBlock = Block::LEAVES; $this->leafType = Leaves::SPRUCE; $this->type = Wood::SPRUCE; $this->treeHeight = 10; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random */ public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ $this->treeHeight = $random->nextBoundedInt(4) + 6; $topSize = $this->treeHeight - (1 + $random->nextBoundedInt(2)); $lRadius = 2 + $random->nextBoundedInt(2); $this->placeTrunk($level, $x, $y, $z, $random, $this->treeHeight - $random->nextBoundedInt(3)); $radius = $random->nextBoundedInt(2); $maxR = 1; $minR = 0; for($yy = 0; $yy <= $topSize; ++$yy){ $yyy = $y + $this->treeHeight - $yy; for($xx = $x - $radius; $xx <= $x + $radius; ++$xx){ $xOff = abs($xx - $x); for($zz = $z - $radius; $zz <= $z + $radius; ++$zz){ $zOff = abs($zz - $z); if($xOff === $radius and $zOff === $radius and $radius > 0){ continue; } if(!Block::$solid[$level->getBlockIdAt($xx, $yyy, $zz)]){ $level->setBlockIdAt($xx, $yyy, $zz, $this->leafBlock); $level->setBlockDataAt($xx, $yyy, $zz, $this->type); } } } if($radius >= $maxR){ $radius = $minR; $minR = 1; if(++$maxR > $lRadius){ $maxR = $lRadius; } }else{ ++$radius; } } } }nextRange($pos->x - $radius, $pos->x + $radius); $z = $random->nextRange($pos->z - $radius, $pos->z + $radius); if($level->getBlockIdAt($x, $pos->y + 1, $z) === Block::AIR and $level->getBlockIdAt($x, $pos->y, $z) === Block::GRASS){ $t = $arr[$random->nextRange(0, $arrC)]; $level->setBlockIdAt($x, $pos->y + 1, $z, $t[0]); $level->setBlockDataAt($x, $pos->y + 1, $z, $t[1]); } } } } true, 6 => true, 17 => true, 18 => true, Block::SNOW_LAYER => true, Block::LOG2 => true, Block::LEAVES2 => true ]; public $type = 0; public $trunkBlock = Block::LOG; public $leafBlock = Block::LEAVES; public $treeHeight = 7; public $leafType = 0; /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random * @param int $type * @param bool $noBigTree */ public static function growTree(ChunkManager $level, $x, $y, $z, Random $random, $type = 0, bool $noBigTree = true){ switch($type){ case Sapling::SPRUCE: $tree = new SpruceTree(); break; case Sapling::BIRCH: if($random->nextBoundedInt(39) === 0){ $tree = new BirchTree(true); }else{ $tree = new BirchTree(); } break; case Sapling::JUNGLE: $tree = new JungleTree(); break; case Sapling::ACACIA: $tree = new AcaciaTree(); break; case Sapling::DARK_OAK: $tree = new DarkOakTree(); break; case Sapling::OAK: default: if(!$noBigTree and $random->nextRange(0, 9) === 0){ $tree = new BigTree(); }else{ $tree = new OakTree(); } break; } if($tree->canPlaceObject($level, $x, $y, $z, $random)){ $tree->placeObject($level, $x, $y, $z, $random); } } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random * * @return bool */ public function canPlaceObject(ChunkManager $level, $x, $y, $z, Random $random){ $radiusToCheck = 0; for($yy = 0; $yy < $this->treeHeight + 3; ++$yy){ if($yy == 1 or $yy === $this->treeHeight){ ++$radiusToCheck; } for($xx = -$radiusToCheck; $xx < ($radiusToCheck + 1); ++$xx){ for($zz = -$radiusToCheck; $zz < ($radiusToCheck + 1); ++$zz){ if(!isset($this->overridable[$level->getBlockIdAt($x + $xx, $y + $yy, $z + $zz)])){ return false; } } } } return true; } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random */ public function placeObject(ChunkManager $level, $x, $y, $z, Random $random){ $this->placeTrunk($level, $x, $y, $z, $random, $this->treeHeight - 1); for($yy = $y - 3 + $this->treeHeight; $yy <= $y + $this->treeHeight; ++$yy){ $yOff = $yy - ($y + $this->treeHeight); $mid = (int) (1 - $yOff / 2); for($xx = $x - $mid; $xx <= $x + $mid; ++$xx){ $xOff = abs($xx - $x); for($zz = $z - $mid; $zz <= $z + $mid; ++$zz){ $zOff = abs($zz - $z); if($xOff === $mid and $zOff === $mid and ($yOff === 0 or $random->nextBoundedInt(2) === 0)){ continue; } if(!Block::$solid[$level->getBlockIdAt($xx, $yy, $zz)]){ $level->setBlockIdAt($xx, $yy, $zz, $this->leafBlock); $level->setBlockDataAt($xx, $yy, $zz, $this->leafType); } } } } } /** * @param ChunkManager $level * @param $x * @param $y * @param $z * @param Random $random * @param $trunkHeight */ protected function placeTrunk(ChunkManager $level, $x, $y, $z, Random $random, $trunkHeight){ // The base dirt block $level->setBlockIdAt($x, $y - 1, $z, Block::DIRT); for($yy = 0; $yy < $trunkHeight; ++$yy){ $blockId = $level->getBlockIdAt($x, $y + $yy, $z); if(isset($this->overridable[$blockId])){ $level->setBlockIdAt($x, $y + $yy, $z, $this->trunkBlock); $level->setBlockDataAt($x, $y + $yy, $z, $this->type); } } } }randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); $tallRand = $random->nextRange(0, 17); $yMax = $y + 1 + (int) ($tallRand > 10) + (int) ($tallRand > 15); if($y !== -1){ for(; $y < 127 and $y < $yMax; $y++){ if($this->canCactusStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::CACTUS); $this->level->setBlockDataAt($x, $y, $z, 1); } } } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canCactusStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); $below = $this->level->getBlockIdAt($x, $y - 1, $z); foreach([$this->level->getBlockIdAt($x + 1, $y, $z), $this->level->getBlockIdAt($x - 1, $y, $z), $this->level->getBlockIdAt($x, $y, $z + 1), $this->level->getBlockIdAt($x, $y, $z - 1)] as $adjacent){ if($adjacent !== Block::AIR) return false; } return ($b === Block::AIR) and ($below === Block::SAND or $below === Block::CACTUS); } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2 and $b !== Block::SNOW_LAYER){ break; } } return $y === 0 ? -1 : ++$y; } } nextInt(); $secondSeed = $random->nextInt(); for($cxx = 0; $cxx < 1; $cxx++){ for($czz = 0; $czz < 1; $czz++){ $dcx = $chunkX + $cxx; $dcz = $chunkZ + $czz; for($cxxx = -$overLap; $cxxx <= $overLap; $cxxx++){ for($czzz = -$overLap; $czzz <= $overLap; $czzz++){ $dcxx = $dcx + $cxxx; $dczz = $dcz + $czzz; $this->pop($level, $dcxx, $dczz, $dcx, $dcz, new Random(($dcxx * $firstSeed) ^ ($dczz * $secondSeed) ^ $random->getSeed())); } } } } } /** * @param ChunkManager $level * @param $x * @param $z * @param $chunkX * @param $chunkZ * @param Random $random */ private function pop(ChunkManager $level, $x, $z, $chunkX, $chunkZ, Random $random){ $c = $level->getChunk($x, $z); $oC = $level->getChunk($chunkX, $chunkZ); if($c == null or $oC == null or ($c != null and !$c->isGenerated()) or ($oC != null and !$oC->isGenerated())){ return; } $chunk = new Vector3($x << 4, 0, $z << 4); $originChunk = new Vector3($chunkX << 4, 0, $chunkZ << 4); if($random->nextBoundedInt(15) != 0){ return; } $numberOfCaves = $random->nextBoundedInt($random->nextBoundedInt($random->nextBoundedInt(40) + 1) + 1); for($caveCount = 0; $caveCount < $numberOfCaves; $caveCount++){ $target = new Vector3($chunk->getX() + $random->nextBoundedInt(16), $random->nextBoundedInt($random->nextBoundedInt(120) + 8), $chunk->getZ() + $random->nextBoundedInt(16)); $numberOfSmallCaves = 1; if($random->nextBoundedInt(4) == 0){ $this->generateLargeCaveBranch($level, $originChunk, $target, new Random($random->nextInt())); $numberOfSmallCaves += $random->nextBoundedInt(4); } for($count = 0; $count < $numberOfSmallCaves; $count++){ $randomHorizontalAngle = $random->nextFloat() * pi() * 2; $randomVerticalAngle = (($random->nextFloat() - 0.5) * 2) / 8; $horizontalScale = $random->nextFloat() * 2 + $random->nextFloat(); if($random->nextBoundedInt(10) == 0){ $horizontalScale *= $random->nextFloat() * $random->nextFloat() * 3 + 1; } $this->generateCaveBranch($level, $originChunk, $target, $horizontalScale, 1, $randomHorizontalAngle, $randomVerticalAngle, 0, 0, new Random($random->nextInt())); } } } /** * @param ChunkManager $level * @param Vector3 $chunk * @param Vector3 $target * @param $horizontalScale * @param $verticalScale * @param $horizontalAngle * @param $verticalAngle * @param int $startingNode * @param int $nodeAmount * @param Random $random */ private function generateCaveBranch(ChunkManager $level, Vector3 $chunk, Vector3 $target, $horizontalScale, $verticalScale, $horizontalAngle, $verticalAngle, int $startingNode, int $nodeAmount, Random $random){ $middle = new Vector3($chunk->getX() + 8, 0, $chunk->getZ() + 8); $horizontalOffset = 0; $verticalOffset = 0; if($nodeAmount <= 0){ $size = 7 * 16; $nodeAmount = $size - $random->nextBoundedInt($size / 4); } $intersectionMode = $random->nextBoundedInt($nodeAmount / 2) + $nodeAmount / 4; $extraVerticalScale = $random->nextBoundedInt(6) == 0; if($startingNode == -1){ $startingNode = $nodeAmount / 2; $lastNode = true; }else{ $lastNode = false; } for(; $startingNode < $nodeAmount; $startingNode++){ $horizontalSize = 1.5 + sin($startingNode * pi() / $nodeAmount) * $horizontalScale; $verticalSize = $horizontalSize * $verticalScale; $target = $target->add(VectorMath::getDirection3D($horizontalAngle, $verticalAngle)); if($extraVerticalScale){ $verticalAngle *= 0.92; }else{ $verticalScale *= 0.7; } $verticalAngle += $verticalOffset * 0.1; $horizontalAngle += $horizontalOffset * 0.1; $verticalOffset *= 0.9; $horizontalOffset *= 0.75; $verticalOffset += ($random->nextFloat() - $random->nextFloat()) * $random->nextFloat() * 2; $horizontalOffset += ($random->nextFloat() - $random->nextFloat()) * $random->nextFloat() * 4; if(!$lastNode){ if($startingNode == $intersectionMode and $horizontalScale > 1 and $nodeAmount > 0){ $this->generateCaveBranch($level, $chunk, $target, $random->nextFloat() * 0.5 + 0.5, 1, $horizontalAngle - pi() / 2, $verticalAngle / 3, $startingNode, $nodeAmount, new Random($random->nextInt())); $this->generateCaveBranch($level, $chunk, $target, $random->nextFloat() * 0.5 + 0.5, 1, $horizontalAngle - pi() / 2, $verticalAngle / 3, $startingNode, $nodeAmount, new Random($random->nextInt())); return; } if($random->nextBoundedInt(4) == 0){ continue; } } $xOffset = $target->getX() - $middle->getX(); $zOffset = $target->getZ() - $middle->getZ(); $nodesLeft = $nodeAmount - $startingNode; $offsetHorizontalScale = $horizontalScale + 18; if((($xOffset * $xOffset + $zOffset * $zOffset) - $nodesLeft * $nodesLeft) > ($offsetHorizontalScale * $offsetHorizontalScale)){ return; } if($target->getX() < ($middle->getX() - 16 - $horizontalSize * 2) or $target->getZ() < ($middle->getZ() - 16 - $horizontalSize * 2) or $target->getX() > ($middle->getX() + 16 + $horizontalSize * 2) or $target->getZ() > ($middle->getZ() + 16 + $horizontalSize * 2) ){ continue; } $start = new Vector3(floor($target->getX() - $horizontalSize) - $chunk->getX() - 1, floor($target->getY() - $verticalSize) - 1, floor($target->getZ() - $horizontalSize) - $chunk->getZ() - 1); $end = new Vector3(floor($target->getX() + $horizontalSize) - $chunk->getX() + 1, floor($target->getY() + $verticalSize) + 1, floor($target->getZ() + $horizontalSize) - $chunk->getZ() + 1); $node = new CaveNode($level, $chunk, $start, $end, $target, $verticalSize, $horizontalSize); if($node->canPlace()){ $node->place(); } if($lastNode){ break; } } } /** * @param ChunkManager $level * @param Vector3 $chunk * @param Vector3 $target * @param Random $random */ private function generateLargeCaveBranch(ChunkManager $level, Vector3 $chunk, Vector3 $target, Random $random){ $this->generateCaveBranch($level, $chunk, $target, $random->nextFloat() * 6 + 1, 0.5, 0, 0, -1, -1, $random); } } class CaveNode { /** @var ChunkManager */ private $level; /** @var Vector3 */ private $chunk; /** @var Vector3 */ private $start; /** @var Vector3 */ private $end; /** @var Vector3 */ private $target; private $verticalSize; private $horizontalSize; /** * CaveNode constructor. * * @param ChunkManager $level * @param Vector3 $chunk * @param Vector3 $start * @param Vector3 $end * @param Vector3 $target * @param $verticalSize * @param $horizontalSize */ public function __construct(ChunkManager $level, Vector3 $chunk, Vector3 $start, Vector3 $end, Vector3 $target, $verticalSize, $horizontalSize){ $this->level = $level; $this->chunk = $chunk; $this->start = $this->clamp($start); $this->end = $this->clamp($end); $this->target = $target; $this->verticalSize = $verticalSize; $this->horizontalSize = $horizontalSize; } /** * @param Vector3 $pos * * @return Vector3 */ private function clamp(Vector3 $pos){ return new Vector3( Math::clamp($pos->getFloorX(), 0, 16), Math::clamp($pos->getFloorY(), 1, 120), Math::clamp($pos->getFloorZ(), 0, 16) ); } /** * @return bool */ public function canPlace(){ for($x = $this->start->getFloorX(); $x < $this->end->getFloorX(); $x++){ for($z = $this->start->getFloorZ(); $z < $this->end->getFloorZ(); $z++){ for($y = $this->end->getFloorY() + 1; $y >= $this->start->getFloorY() - 1; $y--){ $blockId = $this->level->getBlockIdAt($this->chunk->getX() + $x, $y, $this->chunk->getZ() + $z); if($blockId == Block::WATER or $blockId == Block::STILL_WATER){ return false; } if($y != ($this->start->getFloorY() - 1) and $x != ($this->start->getFloorX()) and $x != ($this->end->getFloorX() - 1) and $z != ($this->start->getFloorZ()) and $z != ($this->end->getFloorZ() - 1)){ $y = $this->start->getFloorY(); } } } } return true; } public function place(){ for($x = $this->start->getFloorX(); $x < $this->end->getFloorX(); $x++){ $xOffset = ($this->chunk->getX() + $x + 0.5 - $this->target->getX()) / $this->horizontalSize; for($z = $this->start->getFloorZ(); $z < $this->end->getFloorZ(); $z++){ $zOffset = ($this->chunk->getZ() + $z + 0.5 - $this->target->getZ()) / $this->horizontalSize; if(($xOffset * $xOffset + $zOffset * $zOffset) >= 1){ continue; } for($y = $this->end->getFloorY() - 1; $y >= $this->start->getFloorY(); $y--){ $yOffset = ($y + 0.5 - $this->target->getY()) / $this->verticalSize; if($yOffset > -0.7 and ($xOffset * $xOffset + $yOffset * $yOffset + $zOffset * $zOffset) < 1){ $xx = $this->chunk->getX() + $x; $zz = $this->chunk->getZ() + $z; $blockId = $this->level->getBlockIdAt($xx, $y, $zz); if($blockId == Block::STONE or $blockId == Block::DIRT or $blockId == Block::GRASS){ if($y < 10){ $this->level->setBlockIdAt($xx, $y, $zz, Block::STILL_LAVA); }else{ if($blockId == Block::GRASS and $this->level->getBlockIdAt($xx, $y - 1, $zz) == Block::DIRT){ $this->level->setBlockIdAt($xx, $y - 1, $zz, Block::GRASS); } $this->level->setBlockIdAt($xx, $y, $zz, Block::AIR); } } } } } } } }randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y !== -1 and $this->canDeadBushStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::DEAD_BUSH); $this->level->setBlockDataAt($x, $y, $z, 1); } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canDeadBushStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return ($b === Block::AIR or $b === Block::SNOW_LAYER) and $this->level->getBlockIdAt($x, $y - 1, $z) === Block::SAND; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2 and $b !== Block::SNOW_LAYER){ break; } } return $y === 0 ? -1 : ++$y; } } randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param $type */ public function addType($type){ $this->flowerTypes[] = $type; } /** * @return array */ public function getTypes(){ return $this->flowerTypes; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; if(count($this->flowerTypes) === 0){ $this->addType([Block::DANDELION, 0]); $this->addType([Block::RED_FLOWER, FlowerBlock::TYPE_POPPY]); } $endNum = count($this->flowerTypes) - 1; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y !== -1 and $this->canFlowerStay($x, $y, $z)){ $type = mt_rand(0, $endNum); $this->level->setBlockIdAt($x, $y, $z, $this->flowerTypes[$type][0]); $this->level->setBlockDataAt($x, $y, $z, $this->flowerTypes[$type][1]); } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canFlowerStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return ($b === Block::AIR or $b === Block::SNOW_LAYER) and $this->level->getBlockIdAt($x, $y - 1, $z) === Block::GRASS; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2 and $b !== Block::SNOW_LAYER){ break; } } return $y === 0 ? -1 : ++$y; } }getChunk($chunkX, $chunkZ); if($level instanceof Level or $level instanceof SimpleChunkManager){ $waterHeight = $level->getWaterHeight(); }else $waterHeight = 0; for($x = 0; $x < 16; ++$x){ for($z = 0; $z < 16; ++$z){ $biome = Biome::getBiome($chunk->getBiomeId($x, $z)); $cover = $biome->getGroundCover(); if(count($cover) > 0){ $diffY = 0; if(!$cover[0]->isSolid()){ $diffY = 1; } $column = $chunk->getBlockIdColumn($x, $z); for($y = 127; $y > 0; --$y){ if($column{$y} !== "\x00" and !Block::get(ord($column{$y}))->isTransparent()){ break; } } $startY = min(127, $y + $diffY); $endY = $startY - count($cover); for($y = $startY; $y > $endY and $y >= 0; --$y){ $b = $cover[$startY - $y]; if($column{$y} === "\x00" and $b->isSolid()){ break; } if($y <= $waterHeight and $b->getId() == Block::GRASS and $chunk->getBlockId($x, $y + 1, $z) == Block::STILL_WATER){ $b = Block::get(Block::DIRT); } if($b->getDamage() === 0){ $chunk->setBlockId($x, $y, $z, $b->getId()); }else{ $chunk->setBlock($x, $y, $z, $b->getId(), $b->getDamage()); } } } } } } }randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); //echo "Fire to $x, $y, $z\n"; if($y !== -1 and $this->canGroundFireStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::FIRE); $this->level->updateBlockLight($x, $y, $z); } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canGroundFireStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return ($b === Block::AIR or $b === Block::SNOW_LAYER) and $this->level->getBlockIdAt($x, $y - 1, $z) === 87; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 0; $y <= 127; ++$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b == Block::AIR){ break; } } return $y === 0 ? -1 : $y; } }randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y !== -1 and $this->canLilyPadStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::WATER_LILY); $this->level->setBlockDataAt($x, $y, $z, 1); } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canLilyPadStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return ($b === Block::AIR or $b === Block::SNOW_LAYER) and $this->level->getBlockIdAt($x, $y - 1, $z) === Block::STILL_WATER; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2 and $b !== Block::SNOW_LAYER){ break; } } return $y === 0 ? -1 : ++$y; } } nextRange(0, self::$ODD) === 0){ //$mineshaft = new Mineshaft($random); } } }randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y !== -1 and $this->canMossStoneStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::MOSS_STONE); $this->level->setBlockDataAt($x, $y, $z, 1); } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canMossStoneStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return ($b === Block::AIR or $b === Block::SNOW_LAYER) and $this->level->getBlockIdAt($x, $y - 1, $z) === Block::PODZOL; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2 and $b !== Block::SNOW_LAYER){ break; } } return $y === 0 ? -1 : ++$y; } } level = $level; $type = new OreType(new Glowstone(), 1, 20, 128, 10); $ore = new ObjectOre($random, $type); for($i = 0; $i < $ore->type->clusterCount; ++$i){ $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); $y = $this->getHighestWorkableBlock($x, $z); $ore->placeObject($level, $x, $y, $z); } } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b == 0){ break; } } return $y === 0 ? -1 : ++$y; } }randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ if(mt_rand(0, 100) < 5){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y !== -1 and $this->canNetherLavaStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::LAVA); $this->level->updateBlockLight($x, $y, $z); $this->lavaSpread($x, $y, $z); } } } } /** * @param $x1 * @param $y1 * @param $z1 * @param $x2 * @param $y2 * @param $z2 * * @return int */ private function getFlowDecay($x1, $y1, $z1, $x2, $y2, $z2){ if($this->level->getBlockIdAt($x1, $y1, $z1) !== $this->level->getBlockIdAt($x2, $y2, $z2)){ return -1; }else{ return $this->level->getBlockDataAt($x2, $y2, $z2); } } /** * @param $x * @param $y * @param $z */ private function lavaSpread($x, $y, $z){ if($this->level->getChunk($x >> 4, $z >> 4) == null){ return; } $decay = $this->getFlowDecay($x, $y, $z, $x, $y, $z); $multiplier = 2; if($decay > 0){ $smallestFlowDecay = -100; $smallestFlowDecay = $this->getSmallestFlowDecay($x, $y, $z, $x, $y, $z - 1, $smallestFlowDecay); $smallestFlowDecay = $this->getSmallestFlowDecay($x, $y, $z, $x, $y, $z + 1, $smallestFlowDecay); $smallestFlowDecay = $this->getSmallestFlowDecay($x, $y, $z, $x - 1, $y, $z, $smallestFlowDecay); $smallestFlowDecay = $this->getSmallestFlowDecay($x, $y, $z, $x + 1, $y, $z, $smallestFlowDecay); $k = $smallestFlowDecay + $multiplier; if($k >= 8 or $smallestFlowDecay < 0){ $k = -1; } if(($topFlowDecay = $this->getFlowDecay($x, $y, $z, $x, $y + 1, $z)) >= 0){ if($topFlowDecay >= 8){ $k = $topFlowDecay; }else{ $k = $topFlowDecay | 0x08; } } if($decay < 8 and $k < 8 and $k > 1 and mt_rand(0, 4) !== 0){ $k = $decay; } if($k !== $decay){ $decay = $k; if($decay < 0){ $this->level->setBlockIdAt($x, $y, $z, 0); }else{ $this->level->setBlockIdAt($x, $y, $z, Block::LAVA); $this->level->setBlockDataAt($x, $y, $z, $decay); $this->level->updateBlockLight($x, $y, $z); $this->lavaSpread($x, $y, $z); return; } } } if($this->canFlowInto($x, $y - 1, $z)){ if($decay >= 8){ $this->flowIntoBlock($x, $y - 1, $z, $decay); }else{ $this->flowIntoBlock($x, $y - 1, $z, $decay | 0x08); } }elseif($decay >= 0 and ($decay === 0 or !$this->canFlowInto($x, $y - 1, $z))){ $flags = $this->getOptimalFlowDirections($x, $y, $z); $l = $decay + $multiplier; if($decay >= 8){ $l = 1; } if($l >= 8){ return; } if($flags[0]){ $this->flowIntoBlock($x - 1, $y, $z, $l); } if($flags[1]){ $this->flowIntoBlock($x + 1, $y, $z, $l); } if($flags[2]){ $this->flowIntoBlock($x, $y, $z - 1, $l); } if($flags[3]){ $this->flowIntoBlock($x, $y, $z + 1, $l); } } } /** * @param $x * @param $y * @param $z * @param $newFlowDecay */ private function flowIntoBlock($x, $y, $z, $newFlowDecay){ if($this->level->getBlockIdAt($x, $y, $z) === Block::AIR){ $this->level->setBlockIdAt($x, $y, $z, Block::LAVA); $this->level->setBlockDataAt($x, $y, $z, $newFlowDecay); $this->level->updateBlockLight($x, $y, $z); $this->lavaSpread($x, $y, $z); } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canFlowInto($x, $y, $z){ $id = $this->level->getBlockIdAt($x, $y, $z); if($id === Block::AIR or $id === Block::LAVA or $id === Block::STILL_LAVA){ return true; } return false; } /** * @param $xx * @param $yy * @param $zz * @param $accumulatedCost * @param $previousDirection * * @return int */ private function calculateFlowCost($xx, $yy, $zz, $accumulatedCost, $previousDirection){ $cost = 1000; for($j = 0; $j < 4; ++$j){ if( ($j === 0 and $previousDirection === 1) or ($j === 1 and $previousDirection === 0) or ($j === 2 and $previousDirection === 3) or ($j === 3 and $previousDirection === 2) ){ $x = $xx; $y = $yy; $z = $zz; if($j === 0){ --$x; }elseif($j === 1){ ++$x; }elseif($j === 2){ --$z; }elseif($j === 3){ ++$z; } if(!$this->canFlowInto($x, $y, $z)){ continue; }elseif($this->canFlowInto($x, $y, $z) and $this->level->getBlockDataAt($x, $y, $z) === 0){ continue; }elseif($this->canFlowInto($x, $y - 1, $z)){ return $accumulatedCost; } if($accumulatedCost >= 4){ continue; } $realCost = $this->calculateFlowCost($x, $y, $z, $accumulatedCost + 1, $j); if($realCost < $cost){ $cost = $realCost; } } } return $cost; } /** * @param $xx * @param $yy * @param $zz * * @return array */ private function getOptimalFlowDirections($xx, $yy, $zz){ $flowCost = [0, 0, 0, 0]; $isOptimalFlowDirection = [0, 0, 0, 0]; for($j = 0; $j < 4; ++$j){ $flowCost[$j] = 1000; $x = $xx; $y = $yy; $z = $zz; if($j === 0){ --$x; }elseif($j === 1){ ++$x; }elseif($j === 2){ --$z; }elseif($j === 3){ ++$z; } if(!$this->canFlowInto($x, $y, $z)){ continue; }elseif($this->canFlowInto($x, $y, $z) and $this->level->getBlockDataAt($x, $y, $z) === 0){ continue; }elseif($this->canFlowInto($x, $y - 1, $z)){ $flowCost[$j] = 0; }else{ $flowCost[$j] = $this->calculateFlowCost($x, $y, $z, 1, $j); } } $minCost = $flowCost[0]; for($i = 1; $i < 4; ++$i){ if($flowCost[$i] < $minCost){ $minCost = $flowCost[$i]; } } for($i = 0; $i < 4; ++$i){ $isOptimalFlowDirection[$i] = ($flowCost[$i] === $minCost); } return $isOptimalFlowDirection; } /** * @param $x1 * @param $y1 * @param $z1 * @param $x2 * @param $y2 * @param $z2 * @param $decay * * @return int */ private function getSmallestFlowDecay($x1, $y1, $z1, $x2, $y2, $z2, $decay){ $blockDecay = $this->getFlowDecay($x1, $y1, $z1, $x2, $y2, $z2); if($blockDecay < 0){ return $decay; }elseif($blockDecay === 0){ //Nothing to do! }elseif($blockDecay >= 8){ $blockDecay = 0; } return ($decay >= 0 && $blockDecay >= $decay) ? $decay : $blockDecay; } /** * @param $x * @param $y * @param $z * * @return bool */ private function canNetherLavaStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return $b === Block::AIR; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b == Block::AIR){ break; } } return $y === 0 ? -1 : $y; } }oreTypes as $type){ $ore = new ObjectOre($random, $type); for($i = 0; $i < $ore->type->clusterCount; ++$i){ $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); $y = $random->nextRange($ore->type->minHeight, $ore->type->maxHeight); $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); if($ore->canPlaceObject($level, $x, $y, $z)){ $ore->placeObject($level, $x, $y, $z); } } } } /** * @param array $types */ public function setOreTypes(array $types){ $this->oreTypes = $types; } }oreTypes as $type){ $ore = new ObjectOre($random, $type); for($i = 0; $i < $ore->type->clusterCount; ++$i){ $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); $y = $random->nextRange($ore->type->minHeight, $ore->type->maxHeight); $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); if($ore->canPlaceObject($level, $x, $y, $z)){ $ore->placeObject($level, $x, $y, $z); } } } } /** * @param array $types */ public function setOreTypes(array $types){ $this->oreTypes = $types; } }nextRange(0, $this->waterOdd) === 0){ $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 16); $y = $random->nextBoundedInt(128); $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 16); $pond = new \pocketmine\level\generator\object\Pond($random, new Water()); if($pond->canPlaceObject($level, $x, $y, $z)){ $pond->placeObject($level, $x, $y, $z); } } } /** * @param $waterOdd */ public function setWaterOdd($waterOdd){ $this->waterOdd = $waterOdd; } /** * @param $lavaOdd */ public function setLavaOdd($lavaOdd){ $this->lavaOdd = $lavaOdd; } /** * @param $lavaSurfaceOdd */ public function setLavaSurfaceOdd($lavaSurfaceOdd){ $this->lavaSurfaceOdd = $lavaSurfaceOdd; } }randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); $tallRand = $random->nextRange(0, 17); $yMax = $y + 2 + (int) ($tallRand > 10) + (int) ($tallRand > 15); if($y !== -1){ for(; $y < 127 and $y < $yMax; $y++){ if($this->canSugarcaneStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::SUGARCANE_BLOCK); $this->level->setBlockDataAt($x, $y, $z, 1); } } } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canSugarcaneStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); $below = $this->level->getBlockIdAt($x, $y - 1, $z); $water = false; foreach([$this->level->getBlockIdAt($x + 1, $y - 1, $z), $this->level->getBlockIdAt($x - 1, $y - 1, $z), $this->level->getBlockIdAt($x, $y - 1, $z + 1), $this->level->getBlockIdAt($x, $y - 1, $z - 1)] as $adjacent){ if($adjacent === Block::WATER or $adjacent === Block::STILL_WATER){ $water = true; break; } } return ($b === Block::AIR) and ((($below === Block::SAND or $below === Block::GRASS) and $water) or ($below === Block::SUGARCANE_BLOCK)); } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2){ break; } } return $y === 0 ? -1 : ++$y; } } randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y !== -1 and $this->canTallGrassStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::TALL_GRASS); $this->level->setBlockDataAt($x, $y, $z, 1); } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canTallGrassStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return ($b === Block::AIR or $b === Block::SNOW_LAYER) and $this->level->getBlockIdAt($x, $y - 1, $z) === Block::GRASS; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2 and $b !== Block::SNOW_LAYER){ break; } } return $y === 0 ? -1 : ++$y; } }type = $type; } /** * @param $amount */ public function setRandomAmount($amount){ $this->randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX << 4, ($chunkX << 4) + 15); $z = $random->nextRange($chunkZ << 4, ($chunkZ << 4) + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y === -1){ continue; } ObjectTree::growTree($this->level, $x, $y, $z, $random, $this->type); } } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 127; $y > 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b === Block::DIRT or $b === Block::GRASS or $b === Block::PODZOL){ break; }elseif($b !== 0 and $b !== Block::SNOW_LAYER){ return -1; } } return ++$y; } } randomAmount = $amount; } /** * @param $amount */ public function setBaseAmount($amount){ $this->baseAmount = $amount; } /** * @param ChunkManager $level * @param $chunkX * @param $chunkZ * @param Random $random * * @return mixed|void */ public function populate(ChunkManager $level, $chunkX, $chunkZ, Random $random){ $this->level = $level; $amount = $random->nextRange(0, $this->randomAmount + 1) + $this->baseAmount; for($i = 0; $i < $amount; ++$i){ $x = $random->nextRange($chunkX * 16, $chunkX * 16 + 15); $z = $random->nextRange($chunkZ * 16, $chunkZ * 16 + 15); $y = $this->getHighestWorkableBlock($x, $z); if($y !== -1 and $this->canWaterPitStay($x, $y, $z)){ $this->level->setBlockIdAt($x, $y, $z, Block::STILL_WATER); $this->level->setBlockDataAt($x, $y, $z, 8); } } } /** * @param $x * @param $y * @param $z * * @return bool */ private function canWaterPitStay($x, $y, $z){ $b = $this->level->getBlockIdAt($x, $y, $z); return ($b === Block::AIR or $b === Block::GRASS) and $this->level->getBlockIdAt($x, $y, $z) === Block::DIRT; } /** * @param $x * @param $z * * @return int */ private function getHighestWorkableBlock($x, $z){ for($y = 61; $y >= 0; --$y){ $b = $this->level->getBlockIdAt($x, $y, $z); if($b !== Block::AIR and $b !== Block::LEAVES and $b !== Block::LEAVES2 and $b !== Block::SNOW_LAYER){ break; } } return $y === 0 ? -1 : ++$y; } } x, $pos->y, $pos->z); $this->data = $b->getId() | ($b->getDamage() << 8); } /** * @return LevelEventPacket */ public function encode(){ $pk = new LevelEventPacket; $pk->evid = LevelEventPacket::EVENT_PARTICLE_DESTROY; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->data = $this->data; return $pk; } } x, $pos->y, $pos->z); $this->text = $text; $this->title = $title; } /** * @return int */ public function getText(){ return $this->text; } /** * @return string */ public function getTitle(){ return $this->title; } /** * @param $text */ public function setText($text){ $this->text = $text; } /** * @param $title */ public function setTitle($title){ $this->title = $title; } /** * @return bool */ public function isInvisible(){ return $this->invisible; } /** * @param bool $value */ public function setInvisible($value = true){ $this->invisible = (bool) $value; } /** * @return array */ public function encode(){ $p = []; if($this->entityId === null){ $this->entityId = Entity::$entityCount++; }else{ $pk0 = new RemoveEntityPacket(); $pk0->eid = $this->entityId; $p[] = $pk0; } if(!$this->invisible){ $pk = new AddPlayerPacket(); $pk->uuid = UUID::fromRandom(); $pk->username = $this->title; $pk->eid = $this->entityId; $pk->x = $this->x; $pk->y = $this->y - 0.50; $pk->z = $this->z; $pk->item = Item::get(Item::AIR); $flags = ( (1 << Entity::DATA_FLAG_CAN_SHOW_NAMETAG) | (1 << Entity::DATA_FLAG_ALWAYS_SHOW_NAMETAG) | (1 << Entity::DATA_FLAG_IMMOBILE) ); $pk->metadata = [ Entity::DATA_FLAGS => [Entity::DATA_TYPE_LONG, $flags], Entity::DATA_NAMETAG => [Entity::DATA_TYPE_STRING, $this->title . ($this->text !== "" ? "\n" . $this->text : "")], Entity::DATA_SCALE => [Entity::DATA_TYPE_FLOAT, 0], ]; $p[] = $pk; } return $p; } } x, $pos->y, $pos->z); $this->id = $id & 0xFFF; $this->data = $data; } /** * @return LevelEventPacket */ public function encode(){ $pk = new LevelEventPacket; $pk->evid = LevelEventPacket::EVENT_ADD_PARTICLE_MASK | $this->id; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->data = $this->data; return $pk; } } getId() << 16) | $item->getDamage()); } } x, $pos->y, $pos->z); $this->width = $width; $this->height = $height; } /** * @return LevelEventPacket */ public function encode(){ $pk = new LevelEventPacket; $pk->evid = LevelEventPacket::EVENT_PARTICLE_SPAWN; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->data = ($this->width & 0xff) + (($this->height & 0xff) << 8); return $pk; } } evid = LevelEventPacket::EVENT_PARTICLE_SPLASH; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->data = $this->data; return $pk; } }getDamage() << 8) | $b->getId()); } } getId()); $this->data = $b->getId(); } public function encode(){ $pk = new LevelSoundEventPacket; $pk->sound = $this->id; $pk->pitch = 1; $pk->extraData = $this->data; list($pk->x, $pk->y, $pk->z) = [$this->x, $this->y, $this->z]; return $pk; } } x, $pos->y, $pos->z); $this->id = (int) $id; $this->pitch = (float) $pitch * 1000; } protected $pitch = 0; protected $id; /** * @return float */ public function getPitch(){ return $this->pitch / 1000; } /** * @param $pitch */ public function setPitch($pitch){ $this->pitch = (float) $pitch * 1000; } /** * @return LevelEventPacket */ public function encode(){ $pk = new LevelEventPacket; $pk->evid = $this->id; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->data = (int) $this->pitch; return $pk; } } instrument = $instrument; $this->pitch = $pitch; } /** * @return array */ public function encode(){ $pk = new BlockEventPacket(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->case1 = $this->instrument; $pk->case2 = $this->pitch; $pk2 = new LevelSoundEventPacket(); $pk2->sound = LevelSoundEventPacket::SOUND_NOTE; $pk2->x = $this->x; $pk2->y = $this->y; $pk2->z = $this->z; $pk2->extraData = $this->instrument; $pk2->pitch = $this->pitch; return [$pk, $pk2]; } } x, $pos->y, $pos->z); $this->id = (int) LevelEventPacket::EVENT_SOUND_SPELL; $this->color = ($r << 16 | $g << 8 | $b) & 0xffffff;*/ } /** * @return null */ public function encode(){ return null; /*$pk = new LevelEventPacket; $pk->evid = $this->id; $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->data = $this->color; return $pk;*/ } }level = $level; $this->weatherNow = self::SUNNY; $this->duration = $duration; $this->lastUpdate = $level->getServer()->getTick(); $this->temporalVector = new Vector3(0, 0, 0); } /** * @return bool */ public function canCalculate() : bool{ return $this->canCalculate; } /** * @param bool $canCalc */ public function setCanCalculate(bool $canCalc){ $this->canCalculate = $canCalc; } /** * @param $currentTick */ public function calcWeather($currentTick){ if($this->canCalculate()){ $tickDiff = $currentTick - $this->lastUpdate; $this->duration -= $tickDiff; if($this->duration <= 0){ $duration = mt_rand( min($this->level->getServer()->weatherRandomDurationMin, $this->level->getServer()->weatherRandomDurationMax), max($this->level->getServer()->weatherRandomDurationMin, $this->level->getServer()->weatherRandomDurationMax)); if($this->weatherNow === self::SUNNY){ $weather = $this->randomWeatherData[array_rand($this->randomWeatherData)]; $this->setWeather($weather, $duration); }else{ $weather = self::SUNNY; $this->setWeather($weather, $duration); } } if(($this->weatherNow >= self::RAINY_THUNDER) and ($this->level->getServer()->lightningTime > 0) and is_int($this->duration / $this->level->getServer()->lightningTime)){ $players = $this->level->getPlayers(); if(count($players) > 0){ $p = $players[array_rand($players)]; $x = $p->x + mt_rand(-64, 64); $z = $p->z + mt_rand(-64, 64); $y = $this->level->getHighestBlockAt($x, $z); $this->level->spawnLightning($this->temporalVector->setComponents($x, $y, $z)); } } } $this->lastUpdate = $currentTick; } /** * @param int $wea * @param int $duration */ public function setWeather(int $wea, int $duration = 12000){ $this->level->getServer()->getPluginManager()->callEvent($ev = new WeatherChangeEvent($this->level, $wea, $duration)); if(!$ev->isCancelled()){ $this->weatherNow = $ev->getWeather(); $this->strength1 = mt_rand(90000, 110000); //If we're clearing the weather, it doesn't matter what strength values we set $this->strength2 = mt_rand(30000, 40000); $this->duration = $ev->getDuration(); $this->sendWeatherToAll(); } } /** * @return array */ public function getRandomWeatherData() : array{ return $this->randomWeatherData; } /** * @param array $randomWeatherData */ public function setRandomWeatherData(array $randomWeatherData){ $this->randomWeatherData = $randomWeatherData; } /** * @return int */ public function getWeather() : int{ return $this->weatherNow; } /** * @param $weather * * @return int */ public static function getWeatherFromString($weather){ if(is_int($weather)){ if($weather <= 3){ return $weather; } return self::SUNNY; } switch(strtolower($weather)){ case "clear": case "sunny": case "fine": return self::SUNNY; case "rain": case "rainy": return self::RAINY; case "thunder": return self::THUNDER; case "rain_thunder": case "rainy_thunder": case "storm": return self::RAINY_THUNDER; default: return self::SUNNY; } } /** * @return bool */ public function isSunny() : bool{ return $this->getWeather() === self::SUNNY; } /** * @return bool */ public function isRainy() : bool{ return $this->getWeather() === self::RAINY; } /** * @return bool */ public function isRainyThunder() : bool{ return $this->getWeather() === self::RAINY_THUNDER; } /** * @return bool */ public function isThunder() : bool{ return $this->getWeather() === self::THUNDER; } /** * @return array */ public function getStrength() : array{ return [$this->strength1, $this->strength2]; } /** * @param Player $p */ public function sendWeather(Player $p){ $pks = [ new LevelEventPacket(), new LevelEventPacket() ]; //Set defaults. These will be sent if the case statement defaults. $pks[0]->evid = LevelEventPacket::EVENT_STOP_RAIN; $pks[0]->data = $this->strength1; $pks[1]->evid = LevelEventPacket::EVENT_STOP_THUNDER; $pks[1]->data = $this->strength2; switch($this->weatherNow){ //If the weather is not clear, overwrite the packet values with these case self::RAIN: $pks[0]->evid = LevelEventPacket::EVENT_START_RAIN; $pks[0]->data = $this->strength1; break; case self::RAINY_THUNDER: $pks[0]->evid = LevelEventPacket::EVENT_START_RAIN; $pks[0]->data = $this->strength1; $pks[1]->evid = LevelEventPacket::EVENT_START_THUNDER; $pks[1]->data = $this->strength2; break; case self::THUNDER: $pks[1]->evid = LevelEventPacket::EVENT_START_THUNDER; $pks[1]->data = $this->strength2; break; default: break; } foreach($pks as $pk){ $p->dataPacket($pk); } $p->weatherData = [$this->weatherNow, $this->strength1, $this->strength2]; } public function sendWeatherToAll(){ foreach($this->level->getPlayers() as $player){ $this->sendWeather($player); } } }minX = $minX; $this->minY = $minY; $this->minZ = $minZ; $this->maxX = $maxX; $this->maxY = $maxY; $this->maxZ = $maxZ; } /** * @param $minX * @param $minY * @param $minZ * @param $maxX * @param $maxY * @param $maxZ * * @return $this */ public function setBounds($minX, $minY, $minZ, $maxX, $maxY, $maxZ){ $this->minX = $minX; $this->minY = $minY; $this->minZ = $minZ; $this->maxX = $maxX; $this->maxY = $maxY; $this->maxZ = $maxZ; return $this; } /** * @param $x * @param $y * @param $z * * @return AxisAlignedBB */ public function addCoord($x, $y, $z){ $minX = $this->minX; $minY = $this->minY; $minZ = $this->minZ; $maxX = $this->maxX; $maxY = $this->maxY; $maxZ = $this->maxZ; if($x < 0){ $minX += $x; }elseif($x > 0){ $maxX += $x; } if($y < 0){ $minY += $y; }elseif($y > 0){ $maxY += $y; } if($z < 0){ $minZ += $z; }elseif($z > 0){ $maxZ += $z; } return new AxisAlignedBB($minX, $minY, $minZ, $maxX, $maxY, $maxZ); } /** * @param $x * @param $y * @param $z * * @return AxisAlignedBB */ public function grow($x, $y, $z){ return new AxisAlignedBB($this->minX - $x, $this->minY - $y, $this->minZ - $z, $this->maxX + $x, $this->maxY + $y, $this->maxZ + $z); } /** * @param $x * @param $y * @param $z * * @return $this */ public function expand($x, $y, $z){ $this->minX -= $x; $this->minY -= $y; $this->minZ -= $z; $this->maxX += $x; $this->maxY += $y; $this->maxZ += $z; return $this; } /** * @param $x * @param $y * @param $z * * @return $this */ public function offset($x, $y, $z){ $this->minX += $x; $this->minY += $y; $this->minZ += $z; $this->maxX += $x; $this->maxY += $y; $this->maxZ += $z; return $this; } /** * @param $x * @param $y * @param $z * * @return AxisAlignedBB */ public function shrink($x, $y, $z){ return new AxisAlignedBB($this->minX + $x, $this->minY + $y, $this->minZ + $z, $this->maxX - $x, $this->maxY - $y, $this->maxZ - $z); } /** * @param $x * @param $y * @param $z * * @return $this */ public function contract($x, $y, $z){ $this->minX += $x; $this->minY += $y; $this->minZ += $z; $this->maxX -= $x; $this->maxY -= $y; $this->maxZ -= $z; return $this; } /** * @param AxisAlignedBB $bb * * @return $this */ public function setBB(AxisAlignedBB $bb){ $this->minX = $bb->minX; $this->minY = $bb->minY; $this->minZ = $bb->minZ; $this->maxX = $bb->maxX; $this->maxY = $bb->maxY; $this->maxZ = $bb->maxZ; return $this; } /** * @param $x * @param $y * @param $z * * @return AxisAlignedBB */ public function getOffsetBoundingBox($x, $y, $z){ return new AxisAlignedBB($this->minX + $x, $this->minY + $y, $this->minZ + $z, $this->maxX + $x, $this->maxY + $y, $this->maxZ + $z); } /** * @param AxisAlignedBB $bb * @param $x * * @return mixed */ public function calculateXOffset(AxisAlignedBB $bb, $x){ if($bb->maxY <= $this->minY or $bb->minY >= $this->maxY){ return $x; } if($bb->maxZ <= $this->minZ or $bb->minZ >= $this->maxZ){ return $x; } if($x > 0 and $bb->maxX <= $this->minX){ $x1 = $this->minX - $bb->maxX; if($x1 < $x){ $x = $x1; } } if($x < 0 and $bb->minX >= $this->maxX){ $x2 = $this->maxX - $bb->minX; if($x2 > $x){ $x = $x2; } } return $x; } /** * @param AxisAlignedBB $bb * @param $y * * @return mixed */ public function calculateYOffset(AxisAlignedBB $bb, $y){ if($bb->maxX <= $this->minX or $bb->minX >= $this->maxX){ return $y; } if($bb->maxZ <= $this->minZ or $bb->minZ >= $this->maxZ){ return $y; } if($y > 0 and $bb->maxY <= $this->minY){ $y1 = $this->minY - $bb->maxY; if($y1 < $y){ $y = $y1; } } if($y < 0 and $bb->minY >= $this->maxY){ $y2 = $this->maxY - $bb->minY; if($y2 > $y){ $y = $y2; } } return $y; } /** * @param AxisAlignedBB $bb * @param $z * * @return mixed */ public function calculateZOffset(AxisAlignedBB $bb, $z){ if($bb->maxX <= $this->minX or $bb->minX >= $this->maxX){ return $z; } if($bb->maxY <= $this->minY or $bb->minY >= $this->maxY){ return $z; } if($z > 0 and $bb->maxZ <= $this->minZ){ $z1 = $this->minZ - $bb->maxZ; if($z1 < $z){ $z = $z1; } } if($z < 0 and $bb->minZ >= $this->maxZ){ $z2 = $this->maxZ - $bb->minZ; if($z2 > $z){ $z = $z2; } } return $z; } /** * @param AxisAlignedBB $bb * * @return bool */ public function intersectsWith(AxisAlignedBB $bb){ if($bb->maxX > $this->minX and $bb->minX < $this->maxX){ if($bb->maxY > $this->minY and $bb->minY < $this->maxY){ return $bb->maxZ > $this->minZ and $bb->minZ < $this->maxZ; } } return false; } /** * @param Vector3 $vector * * @return bool */ public function isVectorInside(Vector3 $vector){ if($vector->x <= $this->minX or $vector->x >= $this->maxX){ return false; } if($vector->y <= $this->minY or $vector->y >= $this->maxY){ return false; } return $vector->z > $this->minZ and $vector->z < $this->maxZ; } /** * @return float|int */ public function getAverageEdgeLength(){ return ($this->maxX - $this->minX + $this->maxY - $this->minY + $this->maxZ - $this->minZ) / 3; } /** * @param Vector3 $vector * * @return bool */ public function isVectorInYZ(Vector3 $vector){ return $vector->y >= $this->minY and $vector->y <= $this->maxY and $vector->z >= $this->minZ and $vector->z <= $this->maxZ; } /** * @param Vector3 $vector * * @return bool */ public function isVectorInXZ(Vector3 $vector){ return $vector->x >= $this->minX and $vector->x <= $this->maxX and $vector->z >= $this->minZ and $vector->z <= $this->maxZ; } /** * @param Vector3 $vector * * @return bool */ public function isVectorInXY(Vector3 $vector){ return $vector->x >= $this->minX and $vector->x <= $this->maxX and $vector->y >= $this->minY and $vector->y <= $this->maxY; } /** * @param Vector3 $pos1 * @param Vector3 $pos2 * * @return MovingObjectPosition */ public function calculateIntercept(Vector3 $pos1, Vector3 $pos2){ $v1 = $pos1->getIntermediateWithXValue($pos2, $this->minX); $v2 = $pos1->getIntermediateWithXValue($pos2, $this->maxX); $v3 = $pos1->getIntermediateWithYValue($pos2, $this->minY); $v4 = $pos1->getIntermediateWithYValue($pos2, $this->maxY); $v5 = $pos1->getIntermediateWithZValue($pos2, $this->minZ); $v6 = $pos1->getIntermediateWithZValue($pos2, $this->maxZ); if($v1 !== null and !$this->isVectorInYZ($v1)){ $v1 = null; } if($v2 !== null and !$this->isVectorInYZ($v2)){ $v2 = null; } if($v3 !== null and !$this->isVectorInXZ($v3)){ $v3 = null; } if($v4 !== null and !$this->isVectorInXZ($v4)){ $v4 = null; } if($v5 !== null and !$this->isVectorInXY($v5)){ $v5 = null; } if($v6 !== null and !$this->isVectorInXY($v6)){ $v6 = null; } $vector = null; if($v1 !== null and ($vector === null or $pos1->distanceSquared($v1) < $pos1->distanceSquared($vector))){ $vector = $v1; } if($v2 !== null and ($vector === null or $pos1->distanceSquared($v2) < $pos1->distanceSquared($vector))){ $vector = $v2; } if($v3 !== null and ($vector === null or $pos1->distanceSquared($v3) < $pos1->distanceSquared($vector))){ $vector = $v3; } if($v4 !== null and ($vector === null or $pos1->distanceSquared($v4) < $pos1->distanceSquared($vector))){ $vector = $v4; } if($v5 !== null and ($vector === null or $pos1->distanceSquared($v5) < $pos1->distanceSquared($vector))){ $vector = $v5; } if($v6 !== null and ($vector === null or $pos1->distanceSquared($v6) < $pos1->distanceSquared($vector))){ $vector = $v6; } if($vector === null){ return null; } $f = -1; if($vector === $v1){ $f = 4; }elseif($vector === $v2){ $f = 5; }elseif($vector === $v3){ $f = 0; }elseif($vector === $v4){ $f = 1; }elseif($vector === $v5){ $f = 2; }elseif($vector === $v6){ $f = 3; } return MovingObjectPosition::fromBlock(0, 0, 0, $f, $vector); } /** * @return string */ public function __toString(){ return "AxisAlignedBB({$this->minX}, {$this->minY}, {$this->minZ}, {$this->maxX}, {$this->maxY}, {$this->maxZ})"; } }= $i ? $i : $i - 1; } /** * @param $n * * @return int */ public static function ceilFloat($n){ $i = (int) ($n + 1); return $n >= $i ? $i : $i - 1; } /** * @param $value * @param $low * @param $high * * @return mixed */ public static function clamp($value, $low, $high){ return min($high, max($low, $value)); } /** * @param $a * @param $b * @param $c * * @return array */ public static function solveQuadratic($a, $b, $c) : array{ $x[0] = (-$b + sqrt($b ** 2 - 4 * $a * $c)) / (2 * $a); $x[1] = (-$b - sqrt($b ** 2 - 4 * $a * $c)) / (2 * $a); if($x[0] == $x[1]){ return [$x[0]]; } return $x; } }matrix[(int) $offset]); } /** * @param mixed $offset * * @return mixed */ public function offsetGet($offset){ return $this->matrix[(int) $offset]; } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value){ $this->matrix[(int) $offset] = $value; } /** * @param mixed $offset */ public function offsetUnset($offset){ unset($this->matrix[(int) $offset]); } /** * Matrix constructor. * * @param $rows * @param $columns * @param array $set */ public function __construct($rows, $columns, array $set = []){ $this->rows = max(1, (int) $rows); $this->columns = max(1, (int) $columns); $this->set($set); } /** * @param array $m */ public function set(array $m){ for($r = 0; $r < $this->rows; ++$r){ $this->matrix[$r] = []; for($c = 0; $c < $this->columns; ++$c){ $this->matrix[$r][$c] = isset($m[$r][$c]) ? $m[$r][$c] : 0; } } } /** * @return int|mixed */ public function getRows(){ return ($this->rows); } /** * @return int|mixed */ public function getColumns(){ return ($this->columns); } /** * @param $row * @param $column * @param $value * * @return bool */ public function setElement($row, $column, $value){ if($row > $this->rows or $row < 0 or $column > $this->columns or $column < 0){ return false; } $this->matrix[(int) $row][(int) $column] = $value; return true; } /** * @param $row * @param $column * * @return bool */ public function getElement($row, $column){ if($row > $this->rows or $row < 0 or $column > $this->columns or $column < 0){ return false; } return $this->matrix[(int) $row][(int) $column]; } /** * @return bool */ public function isSquare(){ return $this->rows === $this->columns; } /** * @param Matrix $matrix * * @return bool|Matrix */ public function add(Matrix $matrix){ if($this->rows !== $matrix->getRows() or $this->columns !== $matrix->getColumns()){ return false; } $result = new Matrix($this->rows, $this->columns); for($r = 0; $r < $this->rows; ++$r){ for($c = 0; $c < $this->columns; ++$c){ $result->setElement($r, $c, $this->matrix[$r][$c] + $matrix->getElement($r, $c)); } } return $result; } /** * @param Matrix $matrix * * @return bool|Matrix */ public function substract(Matrix $matrix){ if($this->rows !== $matrix->getRows() or $this->columns !== $matrix->getColumns()){ return false; } $result = clone $this; for($r = 0; $r < $this->rows; ++$r){ for($c = 0; $c < $this->columns; ++$c){ $result->setElement($r, $c, $this->matrix[$r][$c] - $matrix->getElement($r, $c)); } } return $result; } /** * @param $number * * @return Matrix */ public function multiplyScalar($number){ $result = clone $this; for($r = 0; $r < $this->rows; ++$r){ for($c = 0; $c < $this->columns; ++$c){ $result->setElement($r, $c, $this->matrix[$r][$c] * $number); } } return $result; } /** * @param $number * * @return Matrix */ public function divideScalar($number){ $result = clone $this; for($r = 0; $r < $this->rows; ++$r){ for($c = 0; $c < $this->columns; ++$c){ $result->setElement($r, $c, $this->matrix[$r][$c] / $number); } } return $result; } /** * @return Matrix */ public function transpose(){ $result = new Matrix($this->columns, $this->rows); for($r = 0; $r < $this->rows; ++$r){ for($c = 0; $c < $this->columns; ++$c){ $result->setElement($c, $r, $this->matrix[$r][$c]); } } return $result; } //Naive Matrix product, O(n^3) /** * @param Matrix $matrix * * @return bool|Matrix */ public function product(Matrix $matrix){ if($this->columns !== $matrix->getRows()){ return false; } $c = $matrix->getColumns(); $result = new Matrix($this->rows, $c); for($i = 0; $i < $this->rows; ++$i){ for($j = 0; $j < $c; ++$j){ $sum = 0; for($k = 0; $k < $this->columns; ++$k){ $sum += $this->matrix[$i][$k] * $matrix->getElement($k, $j); } $result->setElement($i, $j, $sum); } } return $result; } //Computation of the determinant of 2x2 and 3x3 matrices /** * @return bool|int */ public function determinant(){ if($this->isSquare() !== true){ return false; } switch($this->rows){ case 1: return 0; case 2: return $this->matrix[0][0] * $this->matrix[1][1] - $this->matrix[0][1] * $this->matrix[1][0]; case 3: return $this->matrix[0][0] * $this->matrix[1][1] * $this->matrix[2][2] + $this->matrix[0][1] * $this->matrix[1][2] * $this->matrix[2][0] + $this->matrix[0][2] * $this->matrix[1][0] * $this->matrix[2][1] - $this->matrix[2][0] * $this->matrix[1][1] * $this->matrix[0][2] - $this->matrix[2][1] * $this->matrix[1][2] * $this->matrix[0][0] - $this->matrix[2][2] * $this->matrix[1][0] * $this->matrix[0][1]; } return false; } /** * @return string */ public function __toString(){ $s = ""; for($r = 0; $r < $this->rows; ++$r){ $s .= implode(",", $this->matrix[$r]) . ";"; } return "Matrix({$this->rows}x{$this->columns};" . substr($s, 0, -1) . ")"; } }x = $x; $this->y = $y; } /** * @return int */ public function getX(){ return $this->x; } /** * @return int */ public function getY(){ return $this->y; } /** * @return int */ public function getFloorX(){ return (int) $this->x; } /** * @return int */ public function getFloorY(){ return (int) $this->y; } /** * @param $x * @param int $y * * @return Vector2 */ public function add($x, $y = 0){ if($x instanceof Vector2){ return $this->add($x->x, $x->y); }else{ return new Vector2($this->x + $x, $this->y + $y); } } /** * @param $x * @param int $y * * @return Vector2 */ public function subtract($x, $y = 0){ if($x instanceof Vector2){ return $this->add(-$x->x, -$x->y); }else{ return $this->add(-$x, -$y); } } /** * @return Vector2 */ public function ceil(){ return new Vector2((int) ($this->x + 1), (int) ($this->y + 1)); } /** * @return Vector2 */ public function floor(){ return new Vector2((int) $this->x, (int) $this->y); } /** * @return Vector2 */ public function round(){ return new Vector2(round($this->x), round($this->y)); } /** * @return Vector2 */ public function abs(){ return new Vector2(abs($this->x), abs($this->y)); } /** * @param $number * * @return Vector2 */ public function multiply($number){ return new Vector2($this->x * $number, $this->y * $number); } /** * @param $number * * @return Vector2 */ public function divide($number){ return new Vector2($this->x / $number, $this->y / $number); } /** * @param $x * @param int $y * * @return float */ public function distance($x, $y = 0){ if($x instanceof Vector2){ return sqrt($this->distanceSquared($x->x, $x->y)); }else{ return sqrt($this->distanceSquared($x, $y)); } } /** * @param $x * @param int $y * * @return number */ public function distanceSquared($x, $y = 0){ if($x instanceof Vector2){ return $this->distanceSquared($x->x, $x->y); }else{ return pow($this->x - $x, 2) + pow($this->y - $y, 2); } } /** * @return float */ public function length(){ return sqrt($this->lengthSquared()); } /** * @return int */ public function lengthSquared(){ return $this->x * $this->x + $this->y * $this->y; } /** * @return Vector2 */ public function normalize(){ $len = $this->lengthSquared(); if($len != 0){ return $this->divide(sqrt($len)); } return new Vector2(0, 0); } /** * @param Vector2 $v * * @return int */ public function dot(Vector2 $v){ return $this->x * $v->x + $this->y * $v->y; } /** * @return string */ public function __toString(){ return "Vector2(x=" . $this->x . ",y=" . $this->y . ")"; } /** * @param Random $random * * @return Vector2 */ public static function createRandomDirection(Random $random){ return VectorMath::getDirection2D($random->nextFloat() * 2 * pi()); } }x = $x; $this->y = $y; $this->z = $z; } /** * @return int */ public function getX(){ return $this->x; } /** * @return int */ public function getY(){ return $this->y; } /** * @return int */ public function getZ(){ return $this->z; } /** * @return int */ public function getFloorX(){ return (int) floor($this->x); } /** * @return int */ public function getFloorY(){ return (int) floor($this->y); } /** * @return int */ public function getFloorZ(){ return (int) floor($this->z); } /** * @return int */ public function getRight(){ return $this->x; } /** * @return int */ public function getUp(){ return $this->y; } /** * @return int */ public function getForward(){ return $this->z; } /** * @return int */ public function getSouth(){ return $this->x; } /** * @return int */ public function getWest(){ return $this->z; } /** * @param Vector3|int $x * @param int $y * @param int $z * * @return Vector3 */ public function add($x, $y = 0, $z = 0){ if($x instanceof Vector3){ return new Vector3($this->x + $x->x, $this->y + $x->y, $this->z + $x->z); }else{ return new Vector3($this->x + $x, $this->y + $y, $this->z + $z); } } /** * @param Vector3|int $x * @param int $y * @param int $z * * @return Vector3 */ public function subtract($x = 0, $y = 0, $z = 0){ if($x instanceof Vector3){ return $this->add(-$x->x, -$x->y, -$x->z); }else{ return $this->add(-$x, -$y, -$z); } } /** * @param $number * * @return Vector3 */ public function multiply($number){ return new Vector3($this->x * $number, $this->y * $number, $this->z * $number); } /** * @param $number * * @return Vector3 */ public function divide($number){ return new Vector3($this->x / $number, $this->y / $number, $this->z / $number); } /** * @return Vector3 */ public function ceil(){ return new Vector3((int) ceil($this->x), (int) ceil($this->y), (int) ceil($this->z)); } /** * @return Vector3 */ public function floor(){ return new Vector3((int) floor($this->x), (int) floor($this->y), (int) floor($this->z)); } /** * @return Vector3 */ public function round(){ return new Vector3((int) round($this->x), (int) round($this->y), (int) round($this->z)); } /** * @return Vector3 */ public function abs(){ return new Vector3(abs($this->x), abs($this->y), abs($this->z)); } /** * @param $side * @param int $step * * @return $this|Vector3 */ public function getSide($side, $step = 1){ switch((int) $side){ case Vector3::SIDE_DOWN: return new Vector3($this->x, $this->y - $step, $this->z); case Vector3::SIDE_UP: return new Vector3($this->x, $this->y + $step, $this->z); case Vector3::SIDE_NORTH: return new Vector3($this->x, $this->y, $this->z - $step); case Vector3::SIDE_SOUTH: return new Vector3($this->x, $this->y, $this->z + $step); case Vector3::SIDE_WEST: return new Vector3($this->x - $step, $this->y, $this->z); case Vector3::SIDE_EAST: return new Vector3($this->x + $step, $this->y, $this->z); default: return $this; } } /** * Return a Vector3 instance * * @return Vector3 */ public function asVector3() : Vector3{ return new Vector3($this->x, $this->y, $this->z); } /** * Returns the Vector3 side number opposite the specified one * * @param int $side 0-5 one of the Vector3::SIDE_* constants * * @return int * * @throws \InvalidArgumentException if an invalid side is supplied */ public static function getOppositeSide($side){ switch((int) $side){ case Vector3::SIDE_DOWN: return Vector3::SIDE_UP; case Vector3::SIDE_UP: return Vector3::SIDE_DOWN; case Vector3::SIDE_NORTH: return Vector3::SIDE_SOUTH; case Vector3::SIDE_SOUTH: return Vector3::SIDE_NORTH; case Vector3::SIDE_WEST: return Vector3::SIDE_EAST; case Vector3::SIDE_EAST: return Vector3::SIDE_WEST; default: return -1; } } /** * @param Vector3 $pos * * @return float */ public function distance(Vector3 $pos){ return sqrt($this->distanceSquared($pos)); } /** * @param Vector3 $pos * * @return number */ public function distanceSquared(Vector3 $pos){ return pow($this->x - $pos->x, 2) + pow($this->y - $pos->y, 2) + pow($this->z - $pos->z, 2); } /** * @param int $x * @param int $z * * @return mixed */ public function maxPlainDistance($x = 0, $z = 0){ if($x instanceof Vector3){ return $this->maxPlainDistance($x->x, $x->z); }elseif($x instanceof Vector2){ return $this->maxPlainDistance($x->x, $x->y); }else{ return max(abs($this->x - $x), abs($this->z - $z)); } } /** * @return float */ public function length(){ return sqrt($this->lengthSquared()); } /** * @return int */ public function lengthSquared(){ return $this->x * $this->x + $this->y * $this->y + $this->z * $this->z; } /** * @return Vector3 */ public function normalize(){ $len = $this->lengthSquared(); if($len > 0){ return $this->divide(sqrt($len)); } return new Vector3(0, 0, 0); } /** * @param Vector3 $v * * @return int */ public function dot(Vector3 $v){ return $this->x * $v->x + $this->y * $v->y + $this->z * $v->z; } /** * @param Vector3 $v * * @return Vector3 */ public function cross(Vector3 $v){ return new Vector3( $this->y * $v->z - $this->z * $v->y, $this->z * $v->x - $this->x * $v->z, $this->x * $v->y - $this->y * $v->x ); } /** * @param Vector3 $v * * @return bool */ public function equals(Vector3 $v){ return $this->x == $v->x and $this->y == $v->y and $this->z == $v->z; } /** * Returns a new vector with x value equal to the second parameter, along the line between this vector and the * passed in vector, or null if not possible. * * @param Vector3 $v * @param float $x * * @return Vector3 */ public function getIntermediateWithXValue(Vector3 $v, $x){ $xDiff = $v->x - $this->x; $yDiff = $v->y - $this->y; $zDiff = $v->z - $this->z; if(($xDiff * $xDiff) < 0.0000001){ return null; } $f = ($x - $this->x) / $xDiff; if($f < 0 or $f > 1){ return null; }else{ return new Vector3($this->x + $xDiff * $f, $this->y + $yDiff * $f, $this->z + $zDiff * $f); } } /** * Returns a new vector with y value equal to the second parameter, along the line between this vector and the * passed in vector, or null if not possible. * * @param Vector3 $v * @param float $y * * @return Vector3 */ public function getIntermediateWithYValue(Vector3 $v, $y){ $xDiff = $v->x - $this->x; $yDiff = $v->y - $this->y; $zDiff = $v->z - $this->z; if(($yDiff * $yDiff) < 0.0000001){ return null; } $f = ($y - $this->y) / $yDiff; if($f < 0 or $f > 1){ return null; }else{ return new Vector3($this->x + $xDiff * $f, $this->y + $yDiff * $f, $this->z + $zDiff * $f); } } /** * Returns a new vector with z value equal to the second parameter, along the line between this vector and the * passed in vector, or null if not possible. * * @param Vector3 $v * @param float $z * * @return Vector3 */ public function getIntermediateWithZValue(Vector3 $v, $z){ $xDiff = $v->x - $this->x; $yDiff = $v->y - $this->y; $zDiff = $v->z - $this->z; if(($zDiff * $zDiff) < 0.0000001){ return null; } $f = ($z - $this->z) / $zDiff; if($f < 0 or $f > 1){ return null; }else{ return new Vector3($this->x + $xDiff * $f, $this->y + $yDiff * $f, $this->z + $zDiff * $f); } } /** * @param $x * @param $y * @param $z * * @return Vector3 */ public function setComponents($x, $y, $z){ $this->x = $x; $this->y = $y; $this->z = $z; return $this; } /** * @param Vector3 $pos * @param $x * @param $y * @param $z * * @return $this */ public function fromObjectAdd(Vector3 $pos, $x, $y, $z){ $this->x = $pos->x + $x; $this->y = $pos->y + $y; $this->z = $pos->z + $z; return $this; } /** * @return string */ public function __toString(){ return "Vector3(x=" . $this->x . ",y=" . $this->y . ",z=" . $this->z . ")"; } /** * @param Random $random * * @return Vector3 */ public static function createRandomDirection(Random $random){ return VectorMath::getDirection3D($random->nextFloat() * 2 * pi(), $random->nextFloat() * 2 * pi()); } } owningLevel = $owningLevel; } /** * @param Metadatable $block * @param string $metadataKey * * @return string */ public function disambiguate(Metadatable $block, $metadataKey){ if(!($block instanceof Block)){ throw new \InvalidArgumentException("Argument must be a Block instance"); } return $block->x . ":" . $block->y . ":" . $block->z . ":" . $metadataKey; } /** * @param mixed $block * @param string $metadataKey * * @return MetadataValue[] */ public function getMetadata($block, $metadataKey){ if(!($block instanceof Block)){ throw new \InvalidArgumentException("Object must be a Block"); } if($block->getLevel() === $this->owningLevel){ return parent::getMetadata($block, $metadataKey); }else{ throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName()); } } /** * @param mixed $block * @param string $metadataKey * * @return bool */ public function hasMetadata($block, $metadataKey){ if(!($block instanceof Block)){ throw new \InvalidArgumentException("Object must be a Block"); } if($block->getLevel() === $this->owningLevel){ return parent::hasMetadata($block, $metadataKey); }else{ throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName()); } } /** * @param mixed $block * @param string $metadataKey * @param Plugin $owningPlugin */ public function removeMetadata($block, $metadataKey, Plugin $owningPlugin){ if(!($block instanceof Block)){ throw new \InvalidArgumentException("Object must be a Block"); } if($block->getLevel() === $this->owningLevel){ parent::removeMetadata($block, $metadataKey, $owningPlugin); }else{ throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName()); } } /** * @param mixed $block * @param string $metadataKey * @param MetadataValue $newMetadatavalue */ public function setMetadata($block, $metadataKey, MetadataValue $newMetadatavalue){ if(!($block instanceof Block)){ throw new \InvalidArgumentException("Object must be a Block"); } if($block->getLevel() === $this->owningLevel){ parent::setMetadata($block, $metadataKey, $newMetadatavalue); }else{ throw new \InvalidStateException("Block does not belong to world " . $this->owningLevel->getName()); } } }getId() . ":" . $metadataKey; } }getName()) . ":" . $metadataKey; } }getOwningPlugin(); if($owningPlugin === null){ throw new PluginException("Plugin cannot be null"); } $key = $this->disambiguate($subject, $metadataKey); if(!isset($this->metadataMap[$key])){ //$entry = new \WeakMap(); $this->metadataMap[$key] = new \SplObjectStorage();//$entry; }else{ $entry = $this->metadataMap[$key]; } $entry[$owningPlugin] = $newMetadataValue; } /** * Returns all metadata values attached to an object. If multiple * have attached metadata, each will value will be included. * * @param mixed $subject * @param string $metadataKey * * @return MetadataValue[] * * @throws \Exception */ public function getMetadata($subject, $metadataKey){ $key = $this->disambiguate($subject, $metadataKey); if(isset($this->metadataMap[$key])){ return $this->metadataMap[$key]; }else{ return []; } } /** * Tests to see if a metadata attribute has been set on an object. * * @param mixed $subject * @param string $metadataKey * * @return bool * * @throws \Exception */ public function hasMetadata($subject, $metadataKey){ return isset($this->metadataMap[$this->disambiguate($subject, $metadataKey)]); } /** * Removes a metadata item owned by a plugin from a subject. * * @param mixed $subject * @param string $metadataKey * @param Plugin $owningPlugin * * @throws \Exception */ public function removeMetadata($subject, $metadataKey, Plugin $owningPlugin){ $key = $this->disambiguate($subject, $metadataKey); if(isset($this->metadataMap[$key])){ unset($this->metadataMap[$key][$owningPlugin]); if($this->metadataMap[$key]->count() === 0){ unset($this->metadataMap[$key]); } } } /** * Invalidates all metadata in the metadata store that originates from the * given plugin. Doing this will force each invalidated metadata item to * be recalculated the next time it is accessed. * * @param Plugin $owningPlugin */ public function invalidateAll(Plugin $owningPlugin){ /** @var $values MetadataValue[] */ foreach($this->metadataMap as $values){ if(isset($values[$owningPlugin])){ $values[$owningPlugin]->invalidate(); } } } /** * Creates a unique name for the object receiving metadata by combining * unique data from the subject with a metadataKey. * * @param Metadatable $subject * @param string $metadataKey * * @return string * * @throws \InvalidArgumentException */ public abstract function disambiguate(Metadatable $subject, $metadataKey); } */ protected $owningPlugin; /** * MetadataValue constructor. * * @param Plugin $owningPlugin */ protected function __construct(Plugin $owningPlugin){ $this->owningPlugin = new \WeakRef($owningPlugin); } /** * @return Plugin */ public function getOwningPlugin(){ return $this->owningPlugin->get(); } /** * Fetches the value of this metadata item. * * @return mixed */ public abstract function value(); /** * Invalidates this metadata item, forcing it to recompute when next * accessed. */ public abstract function invalidate(); }getName()) . ":" . $metadataKey; } } /** * Named Binary Tag encoder/decoder */ class NBT { const LITTLE_ENDIAN = 0; const BIG_ENDIAN = 1; const TAG_End = 0; const TAG_Byte = 1; const TAG_Short = 2; const TAG_Int = 3; const TAG_Long = 4; const TAG_Float = 5; const TAG_Double = 6; const TAG_ByteArray = 7; const TAG_String = 8; const TAG_List = 9; const TAG_Compound = 10; const TAG_IntArray = 11; public $buffer; private $offset; public $endianness; private $data; /** * @param ListTag $tag1 * @param ListTag $tag2 * * @return bool */ public static function matchList(ListTag $tag1, ListTag $tag2){ if($tag1->getName() !== $tag2->getName() or $tag1->getCount() !== $tag2->getCount()){ return false; } foreach($tag1 as $k => $v){ if(!($v instanceof Tag)){ continue; } if(!isset($tag2->{$k}) or !($tag2->{$k} instanceof $v)){ return false; } if($v instanceof CompoundTag){ if(!self::matchTree($v, $tag2->{$k})){ return false; } }elseif($v instanceof ListTag){ if(!self::matchList($v, $tag2->{$k})){ return false; } }else{ if($v->getValue() !== $tag2->{$k}->getValue()){ return false; } } } return true; } /** * @param CompoundTag $tag1 * @param CompoundTag $tag2 * * @return bool */ public static function matchTree(CompoundTag $tag1, CompoundTag $tag2){ if($tag1->getName() !== $tag2->getName() or $tag1->getCount() !== $tag2->getCount()){ return false; } foreach($tag1 as $k => $v){ if(!($v instanceof Tag)){ continue; } if(!isset($tag2->{$k}) or !($tag2->{$k} instanceof $v)){ return false; } if($v instanceof CompoundTag){ if(!self::matchTree($v, $tag2->{$k})){ return false; } }elseif($v instanceof ListTag){ if(!self::matchList($v, $tag2->{$k})){ return false; } }else{ if($v->getValue() !== $tag2->{$k}->getValue()){ return false; } } } return true; } /** * @param CompoundTag $tag1 * @param CompoundTag $tag2 * @param bool $override * * @return CompoundTag */ public static function combineCompoundTags(CompoundTag $tag1, CompoundTag $tag2, bool $override = false) : CompoundTag{ $tag1 = clone $tag1; foreach($tag2 as $k => $v){ if(!($v instanceof Tag)){ continue; } if(!isset($tag1->{$k}) or (isset($tag1->{$k}) and $override)){ $tag1->{$k} = clone $v; } } return $tag1; } /** * @param $data * @param int $offset * * @return null|CompoundTag * @throws \Exception */ public static function parseJSON($data, &$offset = 0){ $len = strlen($data); for(; $offset < $len; ++$offset){ $c = $data{$offset}; if($c === "{"){ ++$offset; $data = self::parseCompound($data, $offset); return new CompoundTag("", $data); }elseif($c !== " " and $c !== "\r" and $c !== "\n" and $c !== "\t"){ throw new \Exception("Syntax error: unexpected '$c' at offset $offset"); } } return null; } /** * @param $str * @param int $offset * * @return array */ private static function parseList($str, &$offset = 0){ $len = strlen($str); $key = 0; $value = null; $data = []; for(; $offset < $len; ++$offset){ if($str{$offset - 1} === "]"){ break; }elseif($str{$offset} === "]"){ ++$offset; break; } $value = self::readValue($str, $offset, $type); switch($type){ case NBT::TAG_Byte: $data[$key] = new ByteTag($key, $value); break; case NBT::TAG_Short: $data[$key] = new ShortTag($key, $value); break; case NBT::TAG_Int: $data[$key] = new IntTag($key, $value); break; case NBT::TAG_Long: $data[$key] = new LongTag($key, $value); break; case NBT::TAG_Float: $data[$key] = new FloatTag($key, $value); break; case NBT::TAG_Double: $data[$key] = new DoubleTag($key, $value); break; case NBT::TAG_ByteArray: $data[$key] = new ByteArrayTag($key, $value); break; case NBT::TAG_String: $data[$key] = new StringTag($key, $value); break; case NBT::TAG_List: $data[$key] = new ListTag($key, $value); break; case NBT::TAG_Compound: $data[$key] = new CompoundTag($key, $value); break; case NBT::TAG_IntArray: $data[$key] = new IntArrayTag($key, $value); break; } $key++; } return $data; } /** * @param $str * @param int $offset * * @return array */ private static function parseCompound($str, &$offset = 0){ $len = strlen($str); $data = []; for(; $offset < $len; ++$offset){ if($str{$offset - 1} === "}"){ break; }elseif($str{$offset} === "}"){ ++$offset; break; } $key = self::readKey($str, $offset); $value = self::readValue($str, $offset, $type); switch($type){ case NBT::TAG_Byte: $data[$key] = new ByteTag($key, $value); break; case NBT::TAG_Short: $data[$key] = new ShortTag($key, $value); break; case NBT::TAG_Int: $data[$key] = new IntTag($key, $value); break; case NBT::TAG_Long: $data[$key] = new LongTag($key, $value); break; case NBT::TAG_Float: $data[$key] = new FloatTag($key, $value); break; case NBT::TAG_Double: $data[$key] = new DoubleTag($key, $value); break; case NBT::TAG_ByteArray: $data[$key] = new ByteArrayTag($key, $value); break; case NBT::TAG_String: $data[$key] = new StringTag($key, $value); break; case NBT::TAG_List: $data[$key] = new ListTag($key, $value); break; case NBT::TAG_Compound: $data[$key] = new CompoundTag($key, $value); break; case NBT::TAG_IntArray: $data[$key] = new IntArrayTag($key, $value); break; } } return $data; } /** * @param $data * @param $offset * @param null $type * * @return array|int|string * @throws \Exception */ private static function readValue($data, &$offset, &$type = null){ $value = ""; $type = null; $inQuotes = false; $len = strlen($data); for(; $offset < $len; ++$offset){ $c = $data{$offset}; if(!$inQuotes and ($c === " " or $c === "\r" or $c === "\n" or $c === "\t" or $c === "," or $c === "}" or $c === "]")){ if($c === "," or $c === "}" or $c === "]"){ break; } }elseif($c === '"'){ $inQuotes = !$inQuotes; if($type === null){ $type = self::TAG_String; }elseif($inQuotes){ throw new \Exception("Syntax error: invalid quote at offset $offset"); } }elseif($c === "\\"){ $value .= isset($data{$offset + 1}) ? $data{$offset + 1} : ""; ++$offset; }elseif($c === "{" and !$inQuotes){ if($value !== ""){ throw new \Exception("Syntax error: invalid compound start at offset $offset"); } ++$offset; $value = self::parseCompound($data, $offset); $type = self::TAG_Compound; break; }elseif($c === "[" and !$inQuotes){ if($value !== ""){ throw new \Exception("Syntax error: invalid list start at offset $offset"); } ++$offset; $value = self::parseList($data, $offset); $type = self::TAG_List; break; }else{ $value .= $c; } } if($value === ""){ throw new \Exception("Syntax error: invalid empty value at offset $offset"); } if($type === null and strlen($value) > 0){ $value = trim($value); $last = strtolower(substr($value, -1)); $part = substr($value, 0, -1); if($last !== "b" and $last !== "s" and $last !== "l" and $last !== "f" and $last !== "d"){ $part = $value; $last = null; } if($last !== "f" and $last !== "d" and ((string) ((int) $part)) === $part){ if($last === "b"){ $type = self::TAG_Byte; }elseif($last === "s"){ $type = self::TAG_Short; }elseif($last === "l"){ $type = self::TAG_Long; }else{ $type = self::TAG_Int; } $value = (int) $part; }elseif(is_numeric($part)){ if($last === "f" or $last === "d" or strpos($part, ".") !== false){ if($last === "f"){ $type = self::TAG_Float; }elseif($last === "d"){ $type = self::TAG_Double; }else{ $type = self::TAG_Float; } $value = (float) $part; }else{ if($last === "l"){ $type = self::TAG_Long; }else{ $type = self::TAG_Int; } $value = $part; } }else{ $type = self::TAG_String; } } return $value; } /** * @param $data * @param $offset * * @return string * @throws \Exception */ private static function readKey($data, &$offset){ $key = ""; $len = strlen($data); for(; $offset < $len; ++$offset){ $c = $data{$offset}; if($c === ":"){ ++$offset; break; }elseif($c !== " " and $c !== "\r" and $c !== "\n" and $c !== "\t" and $c !== "\""){ $key .= $c; } } if($key === ""){ throw new \Exception("Syntax error: invalid empty key at offset $offset"); } return $key; } /** * @param $len * * @return bool|string */ public function get($len){ if($len < 0){ $this->offset = strlen($this->buffer) - 1; return ""; }elseif($len === true){ return substr($this->buffer, $this->offset); } return $len === 1 ? $this->buffer{$this->offset++} : substr($this->buffer, ($this->offset += $len) - $len, $len); } /** * @param $v */ public function put($v){ $this->buffer .= $v; } /** * @return bool */ public function feof(){ return !isset($this->buffer{$this->offset}); } /** * NBT constructor. * * @param int $endianness */ public function __construct($endianness = self::LITTLE_ENDIAN){ $this->offset = 0; $this->endianness = $endianness & 0x01; } /** * @param $buffer * @param bool $doMultiple * @param bool $network */ public function read($buffer, $doMultiple = false, bool $network = false){ $this->offset = 0; $this->buffer = $buffer; $this->data = $this->readTag($network); if($doMultiple and $this->offset < strlen($this->buffer)){ $this->data = [$this->data]; do{ $this->data[] = $this->readTag($network); }while($this->offset < strlen($this->buffer)); } $this->buffer = ""; } /** * @param $buffer * @param int $compression */ public function readCompressed($buffer, $compression = ZLIB_ENCODING_GZIP){ $this->read(zlib_decode($buffer)); } /** * @param $buffer * @param int $compression */ public function readNetworkCompressed($buffer, $compression = ZLIB_ENCODING_GZIP){ $this->read(zlib_decode($buffer), false, true); } /** * @param bool $network * * @return string|bool */ public function write(bool $network = false){ $this->offset = 0; $this->buffer = ""; if($this->data instanceof CompoundTag){ $this->writeTag($this->data, $network); return $this->buffer; }elseif(is_array($this->data)){ foreach($this->data as $tag){ $this->writeTag($tag, $network); } return $this->buffer; } return false; } /** * @param int $compression * @param int $level * * @return bool|string */ public function writeCompressed($compression = ZLIB_ENCODING_GZIP, $level = 7){ if(($write = $this->write()) !== false){ return zlib_encode($write, $compression, $level); } return false; } /** * @param int $compression * @param int $level * * @return bool|string */ public function writeNetworkCompressed($compression = ZLIB_ENCODING_GZIP, $level = 7){ if(($write = $this->write(true)) !== false){ return zlib_encode($write, $compression, $level); } return false; } /** * @param bool $network * * @return ByteArrayTag|ByteTag|DoubleTag|FloatTag|IntTag|LongTag|ShortTag */ public function readTag(bool $network = false){ if($this->feof()){ $tagType = -1; //prevent crashes for empty tags }else{ $tagType = $this->getByte(); } switch($tagType){ case NBT::TAG_Byte: $tag = new ByteTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_Short: $tag = new ShortTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_Int: $tag = new IntTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_Long: $tag = new LongTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_Float: $tag = new FloatTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_Double: $tag = new DoubleTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_ByteArray: $tag = new ByteArrayTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_String: $tag = new StringTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_List: $tag = new ListTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_Compound: $tag = new CompoundTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_IntArray: $tag = new IntArrayTag($this->getString($network)); $tag->read($this, $network); break; case NBT::TAG_End: //No named tag default: $tag = new EndTag; break; } return $tag; } /** * @param Tag $tag * @param bool $network */ public function writeTag(Tag $tag, bool $network = false){ $this->putByte($tag->getType()); if($tag instanceof NamedTag){ $this->putString($tag->getName(), $network); } $tag->write($this, $network); } /** * @return int */ public function getByte(){ return Binary::readByte($this->get(1)); } /** * @param $v */ public function putByte($v){ $this->buffer .= Binary::writeByte($v); } /** * @return int */ public function getShort(){ return $this->endianness === self::BIG_ENDIAN ? Binary::readShort($this->get(2)) : Binary::readLShort($this->get(2)); } /** * @param $v */ public function putShort($v){ $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeShort($v) : Binary::writeLShort($v); } /** * @param bool $network * * @return int */ public function getInt(bool $network = false){ if($network === true){ return Binary::readVarInt($this); } return $this->endianness === self::BIG_ENDIAN ? Binary::readInt($this->get(4)) : Binary::readLInt($this->get(4)); } /** * @param $v * @param bool $network */ public function putInt($v, bool $network = false){ if($network === true){ $this->buffer .= Binary::writeVarInt($v); }else{ $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeInt($v) : Binary::writeLInt($v); } } /** * @return int|string */ public function getLong(){ return $this->endianness === self::BIG_ENDIAN ? Binary::readLong($this->get(8)) : Binary::readLLong($this->get(8)); } /** * @param $v */ public function putLong($v){ $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeLong($v) : Binary::writeLLong($v); } /** * @return float */ public function getFloat(){ return $this->endianness === self::BIG_ENDIAN ? Binary::readFloat($this->get(4)) : Binary::readLFloat($this->get(4)); } /** * @param $v */ public function putFloat($v){ $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeFloat($v) : Binary::writeLFloat($v); } /** * @return mixed */ public function getDouble(){ return $this->endianness === self::BIG_ENDIAN ? Binary::readDouble($this->get(8)) : Binary::readLDouble($this->get(8)); } /** * @param $v */ public function putDouble($v){ $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeDouble($v) : Binary::writeLDouble($v); } /** * @param bool $network * * @return bool|string */ public function getString(bool $network = false){ $len = $network ? Binary::readUnsignedVarInt($this) : $this->getShort(); return $this->get($len); } /** * @param $v * @param bool $network */ public function putString($v, bool $network = false){ if($network === true){ $this->put(Binary::writeUnsignedVarInt(strlen($v))); }else{ $this->putShort(strlen($v)); } $this->buffer .= $v; } public function getArray(){ $data = []; self::toArray($data, $this->data); } /** * @param array $data * @param Tag $tag */ private static function toArray(array &$data, Tag $tag){ /** @var CompoundTag[]|ListTag[]|IntArrayTag[] $tag */ foreach($tag as $key => $value){ if($value instanceof CompoundTag or $value instanceof ListTag or $value instanceof IntArrayTag){ $data[$key] = []; self::toArray($data[$key], $value); }else{ $data[$key] = $value->getValue(); } } } /** * @param $key * @param $value * * @return null|ByteTag|FloatTag|IntTag|StringTag */ public static function fromArrayGuesser($key, $value){ if(is_int($value)){ return new IntTag($key, $value); }elseif(is_float($value)){ return new FloatTag($key, $value); }elseif(is_string($value)){ return new StringTag($key, $value); }elseif(is_bool($value)){ return new ByteTag($key, $value ? 1 : 0); } return null; } /** * @param Tag $tag * @param array $data * @param callable $guesser */ private static function fromArray(Tag $tag, array $data, callable $guesser){ foreach($data as $key => $value){ if(is_array($value)){ $isNumeric = true; $isIntArray = true; foreach($value as $k => $v){ if(!is_numeric($k)){ $isNumeric = false; break; }elseif(!is_int($v)){ $isIntArray = false; } } $tag{$key} = $isNumeric ? ($isIntArray ? new IntArrayTag($key, []) : new ListTag($key, [])) : new CompoundTag($key, []); self::fromArray($tag->{$key}, $value, $guesser); }else{ $v = call_user_func($guesser, $key, $value); if($v instanceof Tag){ $tag{$key} = $v; } } } } /** * @param array $data * @param callable|null $guesser */ public function setArray(array $data, callable $guesser = null){ $this->data = new CompoundTag("", []); self::fromArray($this->data, $data, $guesser === null ? [self::class, "fromArrayGuesser"] : $guesser); } /** * @return CompoundTag|array */ public function getData(){ return $this->data; } /** * @param CompoundTag|array $data */ public function setData($data){ $this->data = $data; } } class ByteArrayTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_ByteArray; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->get($nbt->getInt($network)); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putInt(strlen($this->value), $network); $nbt->put($this->value); } } class ByteTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_Byte; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->getByte(); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putByte($this->value); } } class CompoundTag extends NamedTag implements \ArrayAccess { /** * @param string $name * @param NamedTag[] $value */ public function __construct($name = "", $value = []){ $this->__name = $name; foreach($value as $tag){ $this->{$tag->getName()} = $tag; } } /** * @return int */ public function getCount(){ $count = 0; foreach($this as $tag){ if($tag instanceof Tag){ ++$count; } } return $count; } /** * @param mixed $offset * * @return bool */ public function offsetExists($offset){ return isset($this->{$offset}) and $this->{$offset} instanceof Tag; } /** * @param mixed $offset * * @return null */ public function offsetGet($offset){ if(isset($this->{$offset}) and $this->{$offset} instanceof Tag){ if($this->{$offset} instanceof \ArrayAccess){ return $this->{$offset}; }else{ return $this->{$offset}->getValue(); } } return null; } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value){ if($value instanceof Tag){ $this->{$offset} = $value; }elseif(isset($this->{$offset}) and $this->{$offset} instanceof Tag){ $this->{$offset}->setValue($value); } } /** * @param mixed $offset */ public function offsetUnset($offset){ unset($this->{$offset}); } /** * @return int */ public function getType(){ return NBT::TAG_Compound; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = []; do{ $tag = $nbt->readTag($network); if($tag instanceof NamedTag and $tag->getName() !== ""){ $this->{$tag->getName()} = $tag; } }while(!($tag instanceof EndTag) and !$nbt->feof()); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ foreach($this as $tag){ if($tag instanceof Tag and !($tag instanceof EndTag)){ $nbt->writeTag($tag, $network); } } $nbt->writeTag(new EndTag, $network); } /** * @return string */ public function __toString(){ $str = get_class($this) . "{\n"; foreach($this as $tag){ if($tag instanceof Tag){ $str .= get_class($tag) . ":" . $tag->__toString() . "\n"; } } return $str . "}"; } } class DoubleTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_Double; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->getDouble(); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putDouble($this->value); } } class FloatTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_Float; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->getFloat(); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putFloat($this->value); } } class IntArrayTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_IntArray; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $size = $nbt->getInt($network); $this->value = array_values(unpack($nbt->endianness === NBT::LITTLE_ENDIAN ? "V*" : "N*", $nbt->get($size * 4))); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putInt(count($this->value), $network); $nbt->put(pack($nbt->endianness === NBT::LITTLE_ENDIAN ? "V*" : "N*", ...$this->value)); } /** * @return string */ public function __toString(){ $str = get_class($this) . "{\n"; $str .= implode(", ", $this->value); return $str . "}"; } } class IntTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_Int; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->getInt($network); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putInt($this->value, $network); } } class ListTag extends NamedTag implements \ArrayAccess, \Countable { private $tagType; /** * ListTag constructor. * * @param string $name * @param array $value */ public function __construct($name = "", $value = []){ $this->__name = $name; foreach($value as $k => $v){ $this->{$k} = $v; } } /** * @return array */ public function &getValue(){ $value = []; foreach($this as $k => $v){ if($v instanceof Tag){ $value[$k] = $v; } } return $value; } /** * @return int */ public function getCount(){ $count = 0; foreach($this as $tag){ if($tag instanceof Tag){ ++$count; } } return $count; } /** * @param mixed $offset * * @return bool */ public function offsetExists($offset){ return isset($this->{$offset}); } /** * @param mixed $offset * * @return null */ public function offsetGet($offset){ if(isset($this->{$offset}) and $this->{$offset} instanceof Tag){ if($this->{$offset} instanceof \ArrayAccess){ return $this->{$offset}; }else{ return $this->{$offset}->getValue(); } } return null; } /** * @param mixed $offset * @param mixed $value */ public function offsetSet($offset, $value){ if($value instanceof Tag){ $this->{$offset} = $value; }elseif($this->{$offset} instanceof Tag){ $this->{$offset}->setValue($value); } } /** * @param mixed $offset */ public function offsetUnset($offset){ unset($this->{$offset}); } /** * @param int $mode * * @return int */ public function count($mode = COUNT_NORMAL){ for($i = 0; true; $i++){ if(!isset($this->{$i})){ return $i; } if($mode === COUNT_RECURSIVE){ if($this->{$i} instanceof \Countable){ $i += count($this->{$i}); } } } return $i; } /** * @return int */ public function getType(){ return NBT::TAG_List; } /** * @param $type */ public function setTagType($type){ $this->tagType = $type; } /** * @return mixed */ public function getTagType(){ return $this->tagType; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = []; $this->tagType = $nbt->getByte(); $size = $nbt->getInt($network); for($i = 0; $i < $size and !$nbt->feof(); ++$i){ switch($this->tagType){ case NBT::TAG_Byte: $tag = new ByteTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_Short: $tag = new ShortTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_Int: $tag = new IntTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_Long: $tag = new LongTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_Float: $tag = new FloatTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_Double: $tag = new DoubleTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_ByteArray: $tag = new ByteArrayTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_String: $tag = new StringTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_List: $tag = new TagEnum(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_Compound: $tag = new CompoundTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; case NBT::TAG_IntArray: $tag = new IntArrayTag(""); $tag->read($nbt, $network); $this->{$i} = $tag; break; } } } /** * @param NBT $nbt * @param bool $network * * @return bool */ public function write(NBT $nbt, bool $network = false){ if(!isset($this->tagType)){ $id = null; foreach($this as $tag){ if($tag instanceof Tag){ if(!isset($id)){ $id = $tag->getType(); }elseif($id !== $tag->getType()){ return false; } } } $this->tagType = $id; } $nbt->putByte($this->tagType); /** @var Tag[] $tags */ $tags = []; foreach($this as $tag){ if($tag instanceof Tag){ $tags[] = $tag; } } $nbt->putInt(count($tags)); foreach($tags as $tag){ $tag->write($nbt, $network); } return true; } /** * @return string */ public function __toString(){ $str = get_class($this) . "{\n"; foreach($this as $tag){ if($tag instanceof Tag){ $str .= get_class($tag) . ":" . $tag->__toString() . "\n"; } } return $str . "}"; } } class LongTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_Long; } //TODO: check if this also changed to varint /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->getLong(); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putLong($this->value); } }__name = ($name === null or $name === false) ? "" : $name; if($value !== null){ $this->value = $value; } } /** * @return string */ public function getName(){ return $this->__name; } /** * @param $name */ public function setName($name){ $this->__name = $name; } } class ShortTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_Short; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->getShort(); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putShort($this->value); } } class StringTag extends NamedTag { /** * @return int */ public function getType(){ return NBT::TAG_String; } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function read(NBT $nbt, bool $network = false){ $this->value = $nbt->getString($network); } /** * @param NBT $nbt * @param bool $network * * @return mixed|void */ public function write(NBT $nbt, bool $network = false){ $nbt->putString($this->value, $network); } }value; } /** * @return mixed */ public abstract function getType(); /** * @param $value */ public function setValue($value){ $this->value = $value; } /** * @param NBT $nbt * @param bool $network * * @return mixed */ abstract public function write(NBT $nbt, bool $network = false); /** * @param NBT $nbt * @param bool $network * * @return mixed */ abstract public function read(NBT $nbt, bool $network = false); /** * @return string */ public function __toString(){ return (string) $this->value; } } data = $data; $this->targets = serialize($targets); $this->level = $level; } public function onRun(){ try{ $this->final = zlib_encode($this->data, ZLIB_ENCODING_DEFLATE, $this->level); $this->data = null; }catch(\Throwable $e){ } } /** * @param Server $server */ public function onCompletion(Server $server){ $server->broadcastPacketsCallback($this->final, unserialize($this->targets)); } }registerPackets(); $this->server = $server; } /** * @param $upload * @param $download */ public function addStatistics($upload, $download){ $this->upload += $upload; $this->download += $download; } /** * @return int */ public function getUpload(){ return $this->upload; } /** * @return int */ public function getDownload(){ return $this->download; } public function resetStatistics(){ $this->upload = 0; $this->download = 0; } /** * @return SourceInterface[] */ public function getInterfaces(){ return $this->interfaces; } public function processInterfaces(){ foreach($this->interfaces as $interface){ try{ $interface->process(); }catch(\Throwable $e){ $logger = $this->server->getLogger(); if(\pocketmine\DEBUG > 1){ if($logger instanceof MainLogger){ $logger->logException($e); } } $interface->emergencyShutdown(); $this->unregisterInterface($interface); $logger->critical($this->server->getLanguage()->translateString("pocketmine.server.networkError", [get_class($interface), $e->getMessage()])); } } } /** * @param SourceInterface $interface */ public function registerInterface(SourceInterface $interface){ $this->interfaces[$hash = spl_object_hash($interface)] = $interface; if($interface instanceof AdvancedSourceInterface){ $this->advancedInterfaces[$hash] = $interface; $interface->setNetwork($this); } $interface->setName($this->name); } /** * @param SourceInterface $interface */ public function unregisterInterface(SourceInterface $interface){ unset($this->interfaces[$hash = spl_object_hash($interface)], $this->advancedInterfaces[$hash]); } /** * Sets the server name shown on each interface Query * * @param string $name */ public function setName($name){ $this->name = (string) $name; foreach($this->interfaces as $interface){ $interface->setName($this->name); } } public function getName(){ return $this->name; } public function updateName(){ foreach($this->interfaces as $interface){ $interface->setName($this->name); } } /** * @param int $id 0-255 * @param DataPacket $class */ public function registerPacket($id, $class){ $this->packetPool[$id] = new $class; } /** * @return Server */ public function getServer(){ return $this->server; } /** * @param BatchPacket $packet * @param Player $p */ public function processBatch(BatchPacket $packet, Player $p){ try{ if(strlen($packet->payload) === 0){ //prevent zlib_decode errors for incorrectly-decoded packets throw new \InvalidArgumentException("BatchPacket payload is empty or packet decode error"); } $str = zlib_decode($packet->payload, 1024 * 1024 * 64); //Max 64MB $len = strlen($str); if($len === 0){ throw new \InvalidStateException("Decoded BatchPacket payload is empty"); } $stream = new BinaryStream($str); while($stream->offset < $len){ $buf = $stream->getString(); if(($pk = $this->getPacket(ord($buf{0}))) !== null){ if($pk::NETWORK_ID === 0xfe){ throw new \InvalidStateException("Invalid BatchPacket inside BatchPacket"); } $pk->setBuffer($buf, 1); $pk->decode(); assert($pk->feof(), "Still " . strlen(substr($pk->buffer, $pk->offset)) . " bytes unread in " . get_class($pk)); $p->handleDataPacket($pk); } } }catch(\Throwable $e){ if(\pocketmine\DEBUG > 1){ $logger = $this->server->getLogger(); if($logger instanceof MainLogger){ $logger->debug("BatchPacket " . " 0x" . bin2hex($packet->payload)); $logger->logException($e); } } } } /** * @param $id * * @return DataPacket */ public function getPacket($id){ /** @var DataPacket $class */ $class = $this->packetPool[$id]; if($class !== null){ return clone $class; } return null; } /** * @param string $address * @param int $port * @param string $payload */ public function sendPacket($address, $port, $payload){ foreach($this->advancedInterfaces as $interface){ $interface->sendRawPacket($address, $port, $payload); } } /** * Blocks an IP address from the main interface. Setting timeout to -1 will block it forever * * @param string $address * @param int $timeout */ public function blockAddress($address, $timeout = 300){ foreach($this->advancedInterfaces as $interface){ $interface->blockAddress($address, $timeout); } } /** * Unblocks an IP address from the main interface. * * @param string $address */ public function unblockAddress($address){ foreach($this->advancedInterfaces as $interface){ $interface->unblockAddress($address); } } private function registerPackets(){ $this->packetPool = new \SplFixedArray(256); $this->registerPacket(ProtocolInfo::ADD_ENTITY_PACKET, AddEntityPacket::class); $this->registerPacket(ProtocolInfo::ADD_HANGING_ENTITY_PACKET, AddHangingEntityPacket::class); $this->registerPacket(ProtocolInfo::ADD_ITEM_ENTITY_PACKET, AddItemEntityPacket::class); $this->registerPacket(ProtocolInfo::ADD_ITEM_PACKET, AddItemPacket::class); $this->registerPacket(ProtocolInfo::ADD_PAINTING_PACKET, AddPaintingPacket::class); $this->registerPacket(ProtocolInfo::ADD_PLAYER_PACKET, AddPlayerPacket::class); $this->registerPacket(ProtocolInfo::ADVENTURE_SETTINGS_PACKET, AdventureSettingsPacket::class); $this->registerPacket(ProtocolInfo::ANIMATE_PACKET, AnimatePacket::class); $this->registerPacket(ProtocolInfo::AVAILABLE_COMMANDS_PACKET, AvailableCommandsPacket::class); $this->registerPacket(0xfe, BatchPacket::class); $this->registerPacket(ProtocolInfo::BLOCK_ENTITY_DATA_PACKET, BlockEntityDataPacket::class); $this->registerPacket(ProtocolInfo::BLOCK_EVENT_PACKET, BlockEventPacket::class); $this->registerPacket(ProtocolInfo::BOSS_EVENT_PACKET, BossEventPacket::class); $this->registerPacket(ProtocolInfo::CAMERA_PACKET, CameraPacket::class); $this->registerPacket(ProtocolInfo::CHANGE_DIMENSION_PACKET, ChangeDimensionPacket::class); $this->registerPacket(ProtocolInfo::CHUNK_RADIUS_UPDATED_PACKET, ChunkRadiusUpdatedPacket::class); $this->registerPacket(ProtocolInfo::CLIENTBOUND_MAP_ITEM_DATA_PACKET, ClientboundMapItemDataPacket::class); $this->registerPacket(ProtocolInfo::CLIENT_TO_SERVER_HANDSHAKE_PACKET, ClientToServerHandshakePacket::class); $this->registerPacket(ProtocolInfo::COMMAND_STEP_PACKET, CommandStepPacket::class); $this->registerPacket(ProtocolInfo::CONTAINER_CLOSE_PACKET, ContainerClosePacket::class); $this->registerPacket(ProtocolInfo::CONTAINER_OPEN_PACKET, ContainerOpenPacket::class); $this->registerPacket(ProtocolInfo::CONTAINER_SET_CONTENT_PACKET, ContainerSetContentPacket::class); $this->registerPacket(ProtocolInfo::CONTAINER_SET_DATA_PACKET, ContainerSetDataPacket::class); $this->registerPacket(ProtocolInfo::CONTAINER_SET_SLOT_PACKET, ContainerSetSlotPacket::class); $this->registerPacket(ProtocolInfo::CRAFTING_DATA_PACKET, CraftingDataPacket::class); $this->registerPacket(ProtocolInfo::CRAFTING_EVENT_PACKET, CraftingEventPacket::class); $this->registerPacket(ProtocolInfo::DISCONNECT_PACKET, DisconnectPacket::class); $this->registerPacket(ProtocolInfo::DROP_ITEM_PACKET, DropItemPacket::class); $this->registerPacket(ProtocolInfo::ENTITY_EVENT_PACKET, EntityEventPacket::class); $this->registerPacket(ProtocolInfo::EXPLODE_PACKET, ExplodePacket::class); $this->registerPacket(ProtocolInfo::FULL_CHUNK_DATA_PACKET, FullChunkDataPacket::class); $this->registerPacket(ProtocolInfo::HURT_ARMOR_PACKET, HurtArmorPacket::class); $this->registerPacket(ProtocolInfo::INTERACT_PACKET, InteractPacket::class); $this->registerPacket(ProtocolInfo::INVENTORY_ACTION_PACKET, InventoryActionPacket::class); $this->registerPacket(ProtocolInfo::ITEM_FRAME_DROP_ITEM_PACKET, ItemFrameDropItemPacket::class); $this->registerPacket(ProtocolInfo::LEVEL_EVENT_PACKET, LevelEventPacket::class); $this->registerPacket(ProtocolInfo::LEVEL_SOUND_EVENT_PACKET, LevelSoundEventPacket::class); $this->registerPacket(ProtocolInfo::LOGIN_PACKET, LoginPacket::class); $this->registerPacket(ProtocolInfo::MAP_INFO_REQUEST_PACKET, MapInfoRequestPacket::class); $this->registerPacket(ProtocolInfo::MOB_ARMOR_EQUIPMENT_PACKET, MobArmorEquipmentPacket::class); $this->registerPacket(ProtocolInfo::MOB_EQUIPMENT_PACKET, MobEquipmentPacket::class); $this->registerPacket(ProtocolInfo::MOVE_ENTITY_PACKET, MoveEntityPacket::class); $this->registerPacket(ProtocolInfo::MOVE_PLAYER_PACKET, MovePlayerPacket::class); $this->registerPacket(ProtocolInfo::ENTITY_FALL_PACKET, EntityFallPacket::class); $this->registerPacket(ProtocolInfo::PLAYER_ACTION_PACKET, PlayerActionPacket::class); $this->registerPacket(ProtocolInfo::PLAYER_INPUT_PACKET, PlayerInputPacket::class); $this->registerPacket(ProtocolInfo::PLAYER_LIST_PACKET, PlayerListPacket::class); $this->registerPacket(ProtocolInfo::PLAY_STATUS_PACKET, PlayStatusPacket::class); $this->registerPacket(ProtocolInfo::REMOVE_BLOCK_PACKET, RemoveBlockPacket::class); $this->registerPacket(ProtocolInfo::REMOVE_ENTITY_PACKET, RemoveEntityPacket::class); $this->registerPacket(ProtocolInfo::REPLACE_ITEM_IN_SLOT_PACKET, ReplaceItemInSlotPacket::class); $this->registerPacket(ProtocolInfo::REQUEST_CHUNK_RADIUS_PACKET, RequestChunkRadiusPacket::class); $this->registerPacket(ProtocolInfo::RESOURCE_PACK_CHUNK_REQUEST_PACKET, ResourcePackChunkRequestPacket::class); $this->registerPacket(ProtocolInfo::RESOURCE_PACK_CHUNK_DATA_PACKET, ResourcePackChunkDataPacket::class); $this->registerPacket(ProtocolInfo::RESOURCE_PACK_CLIENT_RESPONSE_PACKET, ResourcePackClientResponsePacket::class); $this->registerPacket(ProtocolInfo::RESOURCE_PACK_DATA_INFO_PACKET, ResourcePackDataInfoPacket::class); $this->registerPacket(ProtocolInfo::RESOURCE_PACKS_INFO_PACKET, ResourcePacksInfoPacket::class); $this->registerPacket(ProtocolInfo::RESOURCE_PACK_STACK_PACKET, ResourcePackStackPacket::class); $this->registerPacket(ProtocolInfo::RESPAWN_PACKET, RespawnPacket::class); $this->registerPacket(ProtocolInfo::RIDER_JUMP_PACKET, RiderJumpPacket::class); $this->registerPacket(ProtocolInfo::SHOW_CREDITS_PACKET, ShowCreditsPacket::class); $this->registerPacket(ProtocolInfo::SERVER_TO_CLIENT_HANDSHAKE_PACKET, ServerToClientHandshakePacket::class); $this->registerPacket(ProtocolInfo::SET_COMMANDS_ENABLED_PACKET, SetCommandsEnabledPacket::class); $this->registerPacket(ProtocolInfo::SET_DIFFICULTY_PACKET, SetDifficultyPacket::class); $this->registerPacket(ProtocolInfo::SET_ENTITY_DATA_PACKET, SetEntityDataPacket::class); $this->registerPacket(ProtocolInfo::SET_ENTITY_LINK_PACKET, SetEntityLinkPacket::class); $this->registerPacket(ProtocolInfo::SET_ENTITY_MOTION_PACKET, SetEntityMotionPacket::class); $this->registerPacket(ProtocolInfo::SET_HEALTH_PACKET, SetHealthPacket::class); $this->registerPacket(ProtocolInfo::SET_PLAYER_GAME_TYPE_PACKET, SetPlayerGameTypePacket::class); $this->registerPacket(ProtocolInfo::SET_SPAWN_POSITION_PACKET, SetSpawnPositionPacket::class); $this->registerPacket(ProtocolInfo::SET_TIME_PACKET, SetTimePacket::class); $this->registerPacket(ProtocolInfo::SPAWN_EXPERIENCE_ORB_PACKET, SpawnExperienceOrbPacket::class); $this->registerPacket(ProtocolInfo::START_GAME_PACKET, StartGamePacket::class); $this->registerPacket(ProtocolInfo::TAKE_ITEM_ENTITY_PACKET, TakeItemEntityPacket::class); $this->registerPacket(ProtocolInfo::TEXT_PACKET, TextPacket::class); $this->registerPacket(ProtocolInfo::TRANSFER_PACKET, TransferPacket::class); $this->registerPacket(ProtocolInfo::UPDATE_BLOCK_PACKET, UpdateBlockPacket::class); $this->registerPacket(ProtocolInfo::UPDATE_TRADE_PACKET, UpdateTradePacket::class); $this->registerPacket(ProtocolInfo::USE_ITEM_PACKET, UseItemPacket::class); $this->registerPacket(ProtocolInfo::BLOCK_PICK_REQUEST_PACKET, BlockPickRequestPacket::class); $this->registerPacket(ProtocolInfo::COMMAND_BLOCK_UPDATE_PACKET, CommandBlockUpdatePacket::class); $this->registerPacket(ProtocolInfo::PLAY_SOUND_PACKET, PlaySoundPacket::class); $this->registerPacket(ProtocolInfo::SET_TITLE_PACKET, SetTitlePacket::class); $this->registerPacket(ProtocolInfo::STOP_SOUND_PACKET, StopSoundPacket::class); } } internalData === null ? ($this->internalData = parent::toBinary($internal)) : $this->internalData; } }server = $server; $this->identifiers = []; $this->rakLib = new RakLibServer($this->server->getLogger(), $this->server->getLoader(), $this->server->getPort(), $this->server->getIp() === "" ? "0.0.0.0" : $this->server->getIp()); $this->interface = new ServerHandler($this->rakLib, $this); } /** * @param Network $network */ public function setNetwork(Network $network){ $this->network = $network; } /** * @return bool * @throws \Exception */ public function process(){ $work = false; if($this->interface->handlePacket()){ $work = true; $lasttime = time(); while($this->interface->handlePacket()){ $diff = time() - $lasttime; if($diff >= 1) break; } } if(!$this->rakLib->isRunning() and !$this->rakLib->isShutdown()){ $this->network->unregisterInterface($this); throw new \Exception("RakLib Thread crashed"); } return $work; } /** * @param string $identifier * @param string $reason */ public function closeSession($identifier, $reason){ if(isset($this->players[$identifier])){ $player = $this->players[$identifier]; unset($this->identifiers[spl_object_hash($player)]); unset($this->players[$identifier]); unset($this->identifiersACK[$identifier]); $player->close($player->getLeaveMessage(), $reason); } } /** * @param Player $player * @param string $reason */ public function close(Player $player, $reason = "unknown reason"){ if(isset($this->identifiers[$h = spl_object_hash($player)])){ unset($this->players[$this->identifiers[$h]]); unset($this->identifiersACK[$this->identifiers[$h]]); $this->interface->closeSession($this->identifiers[$h], $reason); unset($this->identifiers[$h]); } } public function shutdown(){ $this->interface->shutdown(); } public function emergencyShutdown(){ $this->interface->emergencyShutdown(); } /** * @param string $identifier * @param string $address * @param int $port * @param int|string $clientID */ public function openSession($identifier, $address, $port, $clientID){ $ev = new PlayerCreationEvent($this, Player::class, Player::class, null, $address, $port); $this->server->getPluginManager()->callEvent($ev); $class = $ev->getPlayerClass(); $player = new $class($this, $ev->getClientId(), $ev->getAddress(), $ev->getPort()); $this->players[$identifier] = $player; $this->identifiersACK[$identifier] = 0; $this->identifiers[spl_object_hash($player)] = $identifier; $this->server->addPlayer($identifier, $player); } /** * @param string $identifier * @param EncapsulatedPacket $packet * @param int $flags */ public function handleEncapsulated($identifier, EncapsulatedPacket $packet, $flags){ if(isset($this->players[$identifier])){ try{ if($packet->buffer !== ""){ $pk = $this->getPacket($packet->buffer); if($pk !== null){ $pk->decode(); assert($pk->feof(), "Still " . strlen(substr($pk->buffer, $pk->offset)) . " bytes unread!"); $this->players[$identifier]->handleDataPacket($pk); } } }catch(\Throwable $e){ $logger = $this->server->getLogger(); if(\pocketmine\DEBUG > 1 and isset($pk)){ $logger->debug("Exception in packet " . get_class($pk) . " 0x" . bin2hex($packet->buffer)); } $logger->logException($e); } } } /** * @param string $address * @param int $timeout */ public function blockAddress($address, $timeout = 300){ $this->interface->blockAddress($address, $timeout); } /** * @param $address */ public function unblockAddress($address){ $this->interface->unblockAddress($address); } /** * @param string $address * @param int $port * @param string $payload */ public function handleRaw($address, $port, $payload){ $this->server->handlePacket($address, $port, $payload); } /** * @param string $address * @param int $port * @param string $payload */ public function sendRawPacket($address, $port, $payload){ $this->interface->sendRaw($address, $port, $payload); } /** * @param string $identifier * @param int $identifierACK */ public function notifyACK($identifier, $identifierACK){ } /** * @param string $name */ public function setName($name){ if($this->server->isDServerEnabled()){ if($this->server->dserverConfig["motdMaxPlayers"] > 0) $pc = $this->server->dserverConfig["motdMaxPlayers"]; elseif($this->server->dserverConfig["motdAllPlayers"]) $pc = $this->server->getDServerMaxPlayers(); else $pc = $this->server->getMaxPlayers(); if($this->server->dserverConfig["motdPlayers"]) $poc = $this->server->getDServerOnlinePlayers(); else $poc = count($this->server->getOnlinePlayers()); }else{ $info = $this->server->getQueryInformation(); $pc = $info->getMaxPlayerCount(); $poc = $info->getPlayerCount(); } $this->interface->sendOption("name", "MCPE;" . rtrim(addcslashes($name, ";"), '\\') . ";" . ProtocolInfo::CURRENT_PROTOCOL . ";" . ProtocolInfo::MINECRAFT_VERSION_NETWORK . ";" . $poc . ";" . $pc ); } /** * @param $name */ public function setPortCheck($name){ $this->interface->sendOption("portChecking", (bool) $name); } /** * @param string $name * @param string $value */ public function handleOption($name, $value){ if($name === "bandwidth"){ $v = unserialize($value); $this->network->addStatistics($v["up"], $v["down"]); } } /** * @param Player $player * @param DataPacket $packet * @param bool $needACK * @param bool $immediate * * @return int|null */ public function putPacket(Player $player, DataPacket $packet, $needACK = false, $immediate = false){ if(isset($this->identifiers[$h = spl_object_hash($player)])){ $identifier = $this->identifiers[$h]; if(!$packet->isEncoded){ $packet->encode(); $packet->isEncoded = true; } if($packet instanceof BatchPacket){ if($needACK){ $pk = new EncapsulatedPacket(); $pk->buffer = $packet->buffer; $pk->reliability = PacketReliability::RELIABLE_ORDERED; $pk->orderChannel = 0; if($needACK === true){ $pk->identifierACK = $this->identifiersACK[$identifier]++; } }else{ if(!isset($packet->__encapsulatedPacket)){ $packet->__encapsulatedPacket = new CachedEncapsulatedPacket; $packet->__encapsulatedPacket->identifierACK = null; $packet->__encapsulatedPacket->buffer = $packet->buffer; // #blameshoghi $packet->__encapsulatedPacket->reliability = PacketReliability::RELIABLE_ORDERED; $packet->__encapsulatedPacket->orderChannel = 0; } $pk = $packet->__encapsulatedPacket; } $this->interface->sendEncapsulated($identifier, $pk, ($needACK === true ? RakLib::FLAG_NEED_ACK : 0) | ($immediate === true ? RakLib::PRIORITY_IMMEDIATE : RakLib::PRIORITY_NORMAL)); return $pk->identifierACK; }else{ $this->server->batchPackets([$player], [$packet], true); return null; } } return null; } /** * @param $buffer * * @return null|DataPacket */ private function getPacket($buffer){ $pid = ord($buffer{0}); if(($data = $this->network->getPacket($pid)) === null){ return null; } $data->setBuffer($buffer, 1); return $data; } } #ifndef COMPILE use pocketmine\entity\Attribute; #endif class AddEntityPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ADD_ENTITY_PACKET; public $eid; public $type; public $x; public $y; public $z; public $speedX; public $speedY; public $speedZ; public $yaw; public $pitch; /** @var Attribute[] */ public $attributes = []; public $metadata = []; public $links = []; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityUniqueID - TODO: verify this $this->putEntityId($this->eid); $this->putUnsignedVarInt($this->type); $this->putVector3f($this->x, $this->y, $this->z); $this->putVector3f($this->speedX, $this->speedY, $this->speedZ); $this->putLFloat($this->pitch * (256 / 360)); $this->putLFloat($this->yaw * (256 / 360)); $this->putUnsignedVarInt(count($this->attributes)); foreach($this->attributes as $entry){ $this->putString($entry->getName()); $this->putLFloat($entry->getMinValue()); $this->putLFloat($entry->getValue()); $this->putLFloat($entry->getMaxValue()); } $this->putEntityMetadata($this->metadata); $this->putUnsignedVarInt(count($this->links)); foreach($this->links as $link){ $this->putEntityId($link[0]); $this->putEntityId($link[1]); $this->putByte($link[2]); } } /** * @return AddEntityPacket|string */ public function getName(){ return "AddEntityPacket"; } } class AddHangingEntityPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ADD_HANGING_ENTITY_PACKET; public $entityUniqueId; public $entityRuntimeId; public $x; public $y; public $z; public $unknown; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityUniqueId); $this->putEntityId($this->entityRuntimeId); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->unknown); } /** * @return AddHangingEntityPacket|string */ public function getName(){ return "AddHangingEntityPacket"; } } class AddItemEntityPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ADD_ITEM_ENTITY_PACKET; public $eid; public $item; public $x; public $y; public $z; public $speedX; public $speedY; public $speedZ; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityUniqueID $this->putEntityId($this->eid); //EntityRuntimeID $this->putSlot($this->item); $this->putVector3f($this->x, $this->y, $this->z); $this->putVector3f($this->speedX, $this->speedY, $this->speedZ); } /** * @return AddItemEntityPacket|string */ public function getName(){ return "AddItemEntityPacket"; } } class AddItemPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ADD_ITEM_PACKET; public $item; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putSlot($this->item); } /** * @return AddItemPacket|string */ public function getName(){ return "AddItemPacket"; } } class AddPaintingPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ADD_PAINTING_PACKET; public $eid; public $x; public $y; public $z; public $direction; public $title; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityUniqueID $this->putEntityId($this->eid); //EntityRuntimeID $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->direction); $this->putString($this->title); } /** * @return string */ public function getName(){ return "AddPaintingPacket"; } } reset(); $this->putUUID($this->uuid); $this->putString($this->username); $this->putEntityId($this->eid); //EntityUniqueID $this->putEntityId($this->eid); //EntityRuntimeID $this->putVector3f($this->x, $this->y, $this->z); $this->putVector3f($this->speedX, $this->speedY, $this->speedZ); $this->putLFloat($this->pitch); $this->putLFloat($this->headYaw ?? $this->yaw); $this->putLFloat($this->yaw); $this->putSlot($this->item); $this->putEntityMetadata($this->metadata); } /** * @return PacketName|string */ public function getName(){ return "AddPlayerPacket"; } } class AdventureSettingsPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ADVENTURE_SETTINGS_PACKET; const PERMISSION_NORMAL = 0; const PERMISSION_OPERATOR = 1; const PERMISSION_HOST = 2; const PERMISSION_AUTOMATION = 3; const PERMISSION_ADMIN = 4; public $worldImmutable = false; public $noPvp = false; public $noPvm = false; public $noMvp = false; public $autoJump = false; public $allowFlight = false; public $noClip = false; public $worldBuilder = false; public $isFlying = false; public $muted = false; public $flags = 0; public $userPermission; /** * */ public function decode(){ $this->flags = $this->getUnsignedVarInt(); $this->userPermission = $this->getUnsignedVarInt(); $this->worldImmutable = (bool) ($this->flags & 1); $this->noPvp = (bool) ($this->flags & (1 << 1)); $this->noPvm = (bool) ($this->flags & (1 << 2)); $this->noMvp = (bool) ($this->flags & (1 << 3)); $this->autoJump = (bool) ($this->flags & (1 << 5)); $this->allowFlight = (bool) ($this->flags & (1 << 6)); $this->noClip = (bool) ($this->flags & (1 << 7)); $this->worldBuilder = (bool) ($this->flags & (1 << 8)); $this->isFlying = (bool) ($this->flags & (1 << 9)); $this->muted = (bool) ($this->flags & (1 << 10)); } /** * */ public function encode(){ $this->reset(); $this->flags |= ((int) $this->worldImmutable); $this->flags |= ((int) $this->noPvp) << 1; $this->flags |= ((int) $this->noPvm) << 2; $this->flags |= ((int) $this->noMvp) << 3; $this->flags |= ((int) $this->autoJump) << 5; $this->flags |= ((int) $this->allowFlight) << 6; $this->flags |= ((int) $this->noClip) << 7; $this->flags |= ((int) $this->worldBuilder) << 8; $this->flags |= ((int) $this->isFlying) << 9; $this->flags |= ((int) $this->muted) << 10; $this->putUnsignedVarInt($this->flags); $this->putUnsignedVarInt($this->userPermission); } } class AnimatePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ANIMATE_PACKET; public $action; public $eid; public $float; /** * */ public function decode(){ $this->action = $this->getVarInt(); $this->eid = $this->getEntityId(); if($this->float & 0x80){ $this->float = $this->getLFloat(); } } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->action); $this->putEntityId($this->eid); if($this->float & 0x80){ $this->putLFloat($this->float); } } } class AvailableCommandsPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::AVAILABLE_COMMANDS_PACKET; public $commands; //JSON-encoded command data public $unknown; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putString($this->commands); $this->putString($this->unknown); } /** * @return PacketName|string */ public function getName(){ return "AvailableCommandsPacket"; } } class BatchPacket extends DataPacket { const NETWORK_ID = 0xfe; public $payload; /** * */ public function decode(){ $this->payload = $this->get(true); } /** * */ public function encode(){ $this->reset(); $this->put($this->payload); } /** * @return PacketName|string */ public function getName(){ return "BatchPacket"; } } class BlockEntityDataPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::BLOCK_ENTITY_DATA_PACKET; public $x; public $y; public $z; public $namedtag; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); $this->namedtag = $this->get(true); } /** * */ public function encode(){ $this->reset(); $this->putBlockCoords($this->x, $this->y, $this->z); $this->put($this->namedtag); } /** * @return PacketName|string */ public function getName(){ return "BlockEntityDataPacket"; } } class BlockEventPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::BLOCK_EVENT_PACKET; public $x; public $y; public $z; public $case1; public $case2; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->case1); $this->putVarInt($this->case2); } /** * @return PacketName|string */ public function getName(){ return "BlockEventPacket"; } } class BlockPickRequestPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::BLOCK_PICK_REQUEST_PACKET; public $x; public $y; public $z; public $unknown; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); $this->unknown = $this->getByte(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "BlockPickRequestPacket"; } } class BossEventPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::BOSS_EVENT_PACKET; public $eid; public $type; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putUnsignedVarInt($this->type); } /** * @return PacketName|string */ public function getName(){ return "BossEventPacket"; } } namespace pocketmine\network\mcpe\protocol; class CameraPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CAMERA_PACKET; public $eid; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->eid); $this->putVarInt($this->eid); } /** * @return PacketName|string */ public function getName(){ return "CameraPacket"; } } class ChangeDimensionPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CHANGE_DIMENSION_PACKET; const DIMENSION_NORMAL = 0; const DIMENSION_NETHER = 1; const DIMENSION_END = 2; public $dimension; public $x; public $y; public $z; public $unknown; //bool public function decode(){ } public function encode(){ $this->reset(); $this->putVarInt($this->dimension); $this->putVector3f($this->x, $this->y, $this->z); $this->putBool($this->unknown); } }reset(); $this->putVarInt($this->radius); } /** * @return PacketName|string */ public function getName(){ return "ChunkRadiusUpdatedPacket"; } } class ClientToServerHandshakePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CLIENT_TO_SERVER_HANDSHAKE_PACKET; /** * @return bool */ public function canBeSentBeforeLogin() : bool{ return true; } /** * */ public function decode(){ //No payload } /** * */ public function encode(){ $this->reset(); //No payload } } use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\utils\Color; class ClientboundMapItemDataPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CLIENTBOUND_MAP_ITEM_DATA_PACKET; const BITFLAG_TEXTURE_UPDATE = 0x02; const BITFLAG_DECORATION_UPDATE = 0x04; const BITFLAG_ENTITY_UPDATE = 0x08; public $mapId; public $type; public $eids = []; public $scale; public $decorations = []; public $width; public $height; public $xOffset = 0; public $yOffset = 0; /** @var Color[][] */ public $colors = []; /** * */ public function decode(){ $this->mapId = $this->getVarInt(); $this->type = $this->getUnsignedVarInt(); if(($this->type & self::BITFLAG_ENTITY_UPDATE) !== 0){ $count = $this->getUnsignedVarInt(); for($i = 0; $i < $count; ++$i){ $this->eids[] = $this->getVarInt(); //entity unique ID, signed var-int } } if(($this->type & (self::BITFLAG_DECORATION_UPDATE | self::BITFLAG_TEXTURE_UPDATE)) !== 0){ //Decoration bitflag or colour bitflag $this->scale = $this->getByte(); } if(($this->type & self::BITFLAG_DECORATION_UPDATE) !== 0){ $count = $this->getUnsignedVarInt(); for($i = 0; $i < $count; ++$i){ $weird = $this->getVarInt(); $this->decorations[$i]["rot"] = $weird & 0x0f; $this->decorations[$i]["img"] = $weird >> 4; $this->decorations[$i]["xOffset"] = $this->getByte(); $this->decorations[$i]["yOffset"] = $this->getByte(); $this->decorations[$i]["label"] = $this->getString(); $this->decorations[$i]["color"] = Color::fromARGB($this->getLInt()); //already BE, don't need to reverse it again } } if(($this->type & self::BITFLAG_TEXTURE_UPDATE) !== 0){ $this->width = $this->getVarInt(); $this->height = $this->getVarInt(); $this->xOffset = $this->getVarInt(); $this->yOffset = $this->getVarInt(); for($y = 0; $y < $this->height; ++$y){ for($x = 0; $x < $this->width; ++$x){ $this->colors[$y][$x] = Color::fromABGR($this->getUnsignedVarInt()); } } } } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->mapId); //entity unique ID, signed var-int $type = 0; if(($eidsCount = count($this->eids)) > 0){ $type |= self::BITFLAG_ENTITY_UPDATE; } if(($decorationCount = count($this->decorations)) > 0){ $type |= self::BITFLAG_DECORATION_UPDATE; } if(count($this->colors) > 0){ $type |= self::BITFLAG_TEXTURE_UPDATE; } $this->putUnsignedVarInt($type); if(($type & self::BITFLAG_ENTITY_UPDATE) !== 0){ //TODO: find out what these are for $this->putUnsignedVarInt($eidsCount); foreach($this->eids as $eid){ $this->putVarInt($eid); } } if(($type & (self::BITFLAG_TEXTURE_UPDATE | self::BITFLAG_DECORATION_UPDATE)) !== 0){ $this->putByte($this->scale); } if(($type & self::BITFLAG_DECORATION_UPDATE) !== 0){ $this->putUnsignedVarInt($decorationCount); foreach($this->decorations as $decoration){ $this->putVarInt(($decoration["rot"] & 0x0f) | ($decoration["img"] << 4)); $this->putByte($decoration["xOffset"]); $this->putByte($decoration["yOffset"]); $this->putString($decoration["label"]); $this->putLInt($decoration["color"]->toARGB()); } } if(($type & self::BITFLAG_TEXTURE_UPDATE) !== 0){ $this->putVarInt($this->width); $this->putVarInt($this->height); $this->putVarInt($this->xOffset); $this->putVarInt($this->yOffset); for($y = 0; $y < $this->height; ++$y){ for($x = 0; $x < $this->width; ++$x){ $this->putUnsignedVarInt($this->colors[$y][$x]->toABGR()); } } } } } class CommandBlockUpdatePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::COMMAND_BLOCK_UPDATE_PACKET; public $isBlock; public $x; public $y; public $z; public $commandBlockMode; public $isRedstoneMode; public $isConditional; public $minecartEid; public $command; public $lastOutput; public $name; public $shouldTrackOutput; /** * */ public function decode(){ $this->isBlock = $this->getBool(); if($this->isBlock){ $this->getBlockPosition($this->x, $this->y, $this->z); $this->commandBlockMode = $this->getUnsignedVarInt(); $this->isRedstoneMode = $this->getBool(); $this->isConditional = $this->getBool(); }else{ //Minecart with command block $this->minecartEid = $this->getEntityRuntimeId(); } $this->command = $this->getString(); $this->lastOutput = $this->getString(); $this->name = $this->getString(); $this->shouldTrackOutput = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putBool($this->isBlock); if($this->isBlock){ $this->putBlockPosition($this->x, $this->y, $this->z); $this->putUnsignedVarInt($this->commandBlockMode); $this->putBool($this->isRedstoneMode); $this->putBool($this->isConditional); }else{ $this->putEntityRuntimeId($this->minecartEid); } $this->putString($this->command); $this->putString($this->lastOutput); $this->putString($this->name); $this->putBool($this->shouldTrackOutput); } } class CommandStepPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::COMMAND_STEP_PACKET; public $command; public $overload; public $uvarint1; public $currentStep; public $done; public $clientId; public $inputJson; public $outputJson; /** * */ public function decode(){ $this->command = $this->getString(); $this->overload = $this->getString(); $this->uvarint1 = $this->getUnsignedVarInt(); $this->currentStep = $this->getUnsignedVarInt(); $this->done = (bool) $this->getByte(); $this->clientId = $this->getUnsignedVarInt(); //TODO: varint64 $this->inputJson = json_decode($this->getString()); $this->outputJson = $this->getString(); $this->get(true); } /** * */ public function encode(){ } } class ContainerClosePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CONTAINER_CLOSE_PACKET; public $windowid; /** * */ public function decode(){ $this->windowid = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); } /** * @return PacketName|string */ public function getName(){ return "ContainerClosePacket"; } } class ContainerOpenPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CONTAINER_OPEN_PACKET; public $windowid; public $type; public $x; public $y; public $z; public $entityId = -1; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); $this->putByte($this->type); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putEntityId($this->entityId); } } class ContainerSetContentPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CONTAINER_SET_CONTENT_PACKET; const SPECIAL_INVENTORY = 0; const SPECIAL_ARMOR = 0x78; const SPECIAL_CREATIVE = 0x79; const SPECIAL_HOTBAR = 0x7a; const SPECIAL_FIXED_INVENTORY = 0x7b; public $windowid; public $targetEid; public $slots = []; public $hotbar = []; /** * @return $this */ public function clean(){ $this->slots = []; $this->hotbar = []; return parent::clean(); } /** * */ public function decode(){ $this->windowid = $this->getUnsignedVarInt(); $this->targetEid = $this->getEntityId(); $count = $this->getUnsignedVarInt(); for($s = 0; $s < $count and !$this->feof(); ++$s){ $this->slots[$s] = $this->getSlot(); } if($this->windowid === self::SPECIAL_INVENTORY){ $count = $this->getUnsignedVarInt(); for($s = 0; $s < $count and !$this->feof(); ++$s){ $this->hotbar[$s] = $this->getVarInt(); } } } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt($this->windowid); $this->putEntityId($this->targetEid); $this->putUnsignedVarInt(count($this->slots)); foreach($this->slots as $slot){ $this->putSlot($slot); } if($this->windowid === self::SPECIAL_INVENTORY and count($this->hotbar) > 0){ $this->putUnsignedVarInt(count($this->hotbar)); foreach($this->hotbar as $slot){ $this->putVarInt($slot); } }else{ $this->putUnsignedVarInt(0); } } /** * @return PacketName|string */ public function getName(){ return "ContainerSetContentPacket"; } } class ContainerSetDataPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CONTAINER_SET_DATA_PACKET; public $windowid; public $property; public $value; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); $this->putVarInt($this->property); $this->putVarInt($this->value); } /** * @return PacketName|string */ public function getName(){ return "ContainerSetDataPacket"; } } use pocketmine\item\Item; class ContainerSetSlotPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CONTAINER_SET_SLOT_PACKET; public $windowid; public $slot; /** @var Item */ public $item; public $hotbarSlot; public $unknown; /** * */ public function decode(){ $this->windowid = $this->getByte(); $this->slot = $this->getVarInt(); $this->hotbarSlot = $this->getVarInt(); $this->item = $this->getSlot(); $this->unknown = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); $this->putVarInt($this->slot); $this->putVarInt($this->hotbarSlot); $this->putSlot($this->item); $this->putByte($this->unknown); } /** * @return PacketName|string */ public function getName(){ return "ContainerSetSlotPacket"; } } use pocketmine\inventory\FurnaceRecipe; use pocketmine\inventory\ShapedRecipe; use pocketmine\inventory\ShapelessRecipe; use pocketmine\item\Item; use pocketmine\utils\BinaryStream; class CraftingDataPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CRAFTING_DATA_PACKET; const ENTRY_SHAPELESS = 0; const ENTRY_SHAPED = 1; const ENTRY_FURNACE = 2; const ENTRY_FURNACE_DATA = 3; const ENTRY_MULTI = 4; /** @var object[] */ public $entries = []; public $cleanRecipes = false; /** * @return $this */ public function clean(){ $this->entries = []; return parent::clean(); } /** * */ public function decode(){ $entries = []; $recipeCount = $this->getUnsignedVarInt(); for($i = 0; $i < $recipeCount; ++$i){ $entry = []; $entry["type"] = $recipeType = $this->getVarInt(); switch($recipeType){ case self::ENTRY_SHAPELESS: $ingredientCount = $this->getUnsignedVarInt(); /** @var Item */ $entry["input"] = []; for($j = 0; $j < $ingredientCount; ++$j){ $entry["input"][] = $this->getSlot(); } $resultCount = $this->getUnsignedVarInt(); $entry["output"] = []; for($k = 0; $k < $resultCount; ++$k){ $entry["output"][] = $this->getSlot(); } $entry["uuid"] = $this->getUUID()->toString(); break; case self::ENTRY_SHAPED: $entry["width"] = $this->getVarInt(); $entry["height"] = $this->getVarInt(); $count = $entry["width"] * $entry["height"]; $entry["input"] = []; for($j = 0; $j < $count; ++$j){ $entry["input"][] = $this->getSlot(); } $resultCount = $this->getUnsignedVarInt(); $entry["output"] = []; for($k = 0; $k < $resultCount; ++$k){ $entry["output"][] = $this->getSlot(); } $entry["uuid"] = $this->getUUID()->toString(); break; case self::ENTRY_FURNACE: case self::ENTRY_FURNACE_DATA: $entry["inputId"] = $this->getVarInt(); if($recipeType === self::ENTRY_FURNACE_DATA){ $entry["inputDamage"] = $this->getVarInt(); } $entry["output"] = $this->getSlot(); break; case self::ENTRY_MULTI: $entry["uuid"] = $this->getUUID()->toString(); break; default: throw new \UnexpectedValueException("Unhandled recipe type $recipeType!"); //do not continue attempting to decode } $entries[] = $entry; } $this->getBool(); //cleanRecipes } /** * @param $entry * @param BinaryStream $stream * * @return int */ private static function writeEntry($entry, BinaryStream $stream){ if($entry instanceof ShapelessRecipe){ return self::writeShapelessRecipe($entry, $stream); }elseif($entry instanceof ShapedRecipe){ return self::writeShapedRecipe($entry, $stream); }elseif($entry instanceof FurnaceRecipe){ return self::writeFurnaceRecipe($entry, $stream); } //TODO: add MultiRecipe return -1; } /** * @param ShapelessRecipe $recipe * @param BinaryStream $stream * * @return int */ private static function writeShapelessRecipe(ShapelessRecipe $recipe, BinaryStream $stream){ $stream->putUnsignedVarInt($recipe->getIngredientCount()); foreach($recipe->getIngredientList() as $item){ $stream->putSlot($item); } $stream->putUnsignedVarInt(1); $stream->putSlot($recipe->getResult()); $stream->putUUID($recipe->getId()); return CraftingDataPacket::ENTRY_SHAPELESS; } /** * @param ShapedRecipe $recipe * @param BinaryStream $stream * * @return int */ private static function writeShapedRecipe(ShapedRecipe $recipe, BinaryStream $stream){ $stream->putVarInt($recipe->getWidth()); $stream->putVarInt($recipe->getHeight()); for($z = 0; $z < $recipe->getHeight(); ++$z){ for($x = 0; $x < $recipe->getWidth(); ++$x){ $stream->putSlot($recipe->getIngredient($x, $z)); } } $stream->putUnsignedVarInt(1); $stream->putSlot($recipe->getResult()); $stream->putUUID($recipe->getId()); return CraftingDataPacket::ENTRY_SHAPED; } /** * @param FurnaceRecipe $recipe * @param BinaryStream $stream * * @return int */ private static function writeFurnaceRecipe(FurnaceRecipe $recipe, BinaryStream $stream){ if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe $stream->putVarInt($recipe->getInput()->getId()); $stream->putVarInt($recipe->getInput()->getDamage()); $stream->putSlot($recipe->getResult()); return CraftingDataPacket::ENTRY_FURNACE_DATA; }else{ $stream->putVarInt($recipe->getInput()->getId()); $stream->putSlot($recipe->getResult()); return CraftingDataPacket::ENTRY_FURNACE; } } /** * @param ShapelessRecipe $recipe */ public function addShapelessRecipe(ShapelessRecipe $recipe){ $this->entries[] = $recipe; } /** * @param ShapedRecipe $recipe */ public function addShapedRecipe(ShapedRecipe $recipe){ $this->entries[] = $recipe; } /** * @param FurnaceRecipe $recipe */ public function addFurnaceRecipe(FurnaceRecipe $recipe){ $this->entries[] = $recipe; } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt(count($this->entries)); $writer = new BinaryStream(); foreach($this->entries as $d){ $entryType = self::writeEntry($d, $writer); if($entryType >= 0){ $this->putVarInt($entryType); $this->put($writer->getBuffer()); }else{ $this->putVarInt(-1); } $writer->reset(); } $this->putBool($this->cleanRecipes); } /** * @return PacketName|string */ public function getName(){ return "CraftingDataPacket"; } } class CraftingEventPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CRAFTING_EVENT_PACKET; public $windowId; public $type; public $id; public $input = []; public $output = []; /** * @return $this */ public function clean(){ $this->input = []; $this->output = []; return parent::clean(); } /** * */ public function decode(){ $this->windowId = $this->getByte(); $this->type = $this->getVarInt(); $this->id = $this->getUUID(); $size = $this->getUnsignedVarInt(); for($i = 0; $i < $size and $i < 128; ++$i){ $this->input[] = $this->getSlot(); } $size = $this->getUnsignedVarInt(); for($i = 0; $i < $size and $i < 128; ++$i){ $this->output[] = $this->getSlot(); } } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "CraftingEventPacket"; } } use pocketmine\entity\Entity; use pocketmine\item\Item; use pocketmine\utils\BinaryStream; use pocketmine\utils\Utils; abstract class DataPacket extends BinaryStream { const NETWORK_ID = 0; public $isEncoded = false; /** * @return int */ public function pid(){ return $this::NETWORK_ID; } /** * @return mixed */ abstract public function encode(); /** * @return mixed */ abstract public function decode(); public function reset(){ $this->buffer = chr($this::NETWORK_ID); $this->offset = 0; } /** * @return $this */ public function clean(){ $this->buffer = null; $this->isEncoded = false; $this->offset = 0; return $this; } /** * @return array */ public function __debugInfo(){ $data = []; foreach($this as $k => $v){ if($k === "buffer"){ $data[$k] = bin2hex($v); }elseif(is_string($v) or (is_object($v) and method_exists($v, "__toString"))){ $data[$k] = Utils::printable((string) $v); }else{ $data[$k] = $v; } } return $data; } /** * @param bool $types * * @return array */ public function getEntityMetadata(bool $types = true) : array{ $count = $this->getUnsignedVarInt(); $data = []; for($i = 0; $i < $count; ++$i){ $key = $this->getUnsignedVarInt(); $type = $this->getUnsignedVarInt(); $value = null; switch($type){ case Entity::DATA_TYPE_BYTE: $value = $this->getByte(); break; case Entity::DATA_TYPE_SHORT: $value = $this->getLShort(true); //signed break; case Entity::DATA_TYPE_INT: $value = $this->getVarInt(); break; case Entity::DATA_TYPE_FLOAT: $value = $this->getLFloat(); break; case Entity::DATA_TYPE_STRING: $value = $this->getString(); break; case Entity::DATA_TYPE_SLOT: //TODO: use objects directly $value = []; $item = $this->getSlot(); $value[0] = $item->getId(); $value[1] = $item->getCount(); $value[2] = $item->getDamage(); break; case Entity::DATA_TYPE_POS: $value = []; $value[0] = $this->getVarInt(); //x $value[1] = $this->getVarInt(); //y (SIGNED) $value[2] = $this->getVarInt(); //z break; case Entity::DATA_TYPE_LONG: $value = $this->getVarInt(); //TODO: varint64 proper support break; case Entity::DATA_TYPE_VECTOR3F: $value = [0.0, 0.0, 0.0]; $this->getVector3f($value[0], $value[1], $value[2]); break; default: $value = []; } if($types === true){ $data[$key] = [$value, $type]; }else{ $data[$key] = $value; } } return $data; } /** * @param array $metadata */ public function putEntityMetadata(array $metadata){ $this->putUnsignedVarInt(count($metadata)); foreach($metadata as $key => $d){ $this->putUnsignedVarInt($key); //data key $this->putUnsignedVarInt($d[0]); //data type switch($d[0]){ case Entity::DATA_TYPE_BYTE: $this->putByte($d[1]); break; case Entity::DATA_TYPE_SHORT: $this->putLShort($d[1]); //SIGNED short! break; case Entity::DATA_TYPE_INT: $this->putVarInt($d[1]); break; case Entity::DATA_TYPE_FLOAT: $this->putLFloat($d[1]); break; case Entity::DATA_TYPE_STRING: $this->putString($d[1]); break; case Entity::DATA_TYPE_SLOT: //TODO: change this implementation (use objects) $this->putSlot(Item::get($d[1][0], $d[1][2], $d[1][1])); //ID, damage, count break; case Entity::DATA_TYPE_POS: //TODO: change this implementation (use objects) $this->putVarInt($d[1][0]); //x $this->putVarInt($d[1][1]); //y (SIGNED) $this->putVarInt($d[1][2]); //z break; case Entity::DATA_TYPE_LONG: $this->putVarInt($d[1]); //TODO: varint64 support break; case Entity::DATA_TYPE_VECTOR3F: //TODO: change this implementation (use objects) $this->putVector3f($d[1][0], $d[1][1], $d[1][2]); //x, y, z } } } /** * @return PacketName|string */ public function getName(){ return "DataPacket"; } } class DisconnectPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::DISCONNECT_PACKET; public $hideDisconnectionScreen = false; public $message; /** * */ public function decode(){ $this->hideDisconnectionScreen = $this->getBool(); $this->message = $this->getString(); } /** * */ public function encode(){ $this->reset(); $this->putBool($this->hideDisconnectionScreen); if(!$this->hideDisconnectionScreen){ $this->putString($this->message); } } } class DropItemPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::DROP_ITEM_PACKET; public $type; public $item; /** * */ public function decode(){ $this->type = $this->getByte(); $this->item = $this->getSlot(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "DropItemPacket"; } } class EntityEventPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ENTITY_EVENT_PACKET; const HURT_ANIMATION = 2; const DEATH_ANIMATION = 3; const TAME_FAIL = 6; const TAME_SUCCESS = 7; const SHAKE_WET = 8; const USE_ITEM = 9; const EAT_GRASS_ANIMATION = 10; const FISH_HOOK_BUBBLE = 11; const FISH_HOOK_POSITION = 12; const FISH_HOOK_HOOK = 13; const FISH_HOOK_TEASE = 14; const SQUID_INK_CLOUD = 15; const AMBIENT_SOUND = 16; const RESPAWN = 17; //TODO add new events public $eid; public $event; public $unknown; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->event = $this->getByte(); $this->unknown = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putByte($this->event); $this->putVarInt($this->unknown); } /** * @return PacketName|string */ public function getName(){ return "EntityEventPacket"; } } class EntityFallPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ENTITY_FALL_PACKET; public $entityRuntimeId; public $fallDistance; public $bool1; /** * */ public function decode(){ $this->entityRuntimeId = $this->getEntityId(); $this->fallDistance = $this->getLFloat(); $this->bool1 = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityRuntimeId); $this->putLFloat($this->fallDistance); $this->putBool($this->bool1); } } class ExplodePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::EXPLODE_PACKET; public $x; public $y; public $z; public $radius; public $records = []; /** * @return $this */ public function clean(){ $this->records = []; return parent::clean(); } /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVector3f($this->x, $this->y, $this->z); $this->putLFloat($this->radius); $this->putUnsignedVarInt(count($this->records)); if(count($this->records) > 0){ foreach($this->records as $record){ $this->putBlockCoords($record->x, $record->y, $record->z); } } } /** * @return PacketName|string */ public function getName(){ return "ExplodePacket"; } } class FullChunkDataPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::FULL_CHUNK_DATA_PACKET; public $chunkX; public $chunkZ; public $data; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->chunkX); $this->putVarInt($this->chunkZ); $this->putString($this->data); } /** * @return PacketName|string */ public function getName(){ return "FullChunkDataPacket"; } } class HurtArmorPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::HURT_ARMOR_PACKET; public $health; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->health); } /** * @return PacketName|string */ public function getName(){ return "HurtArmorPacket"; } } class InteractPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::INTERACT_PACKET; const ACTION_RIGHT_CLICK = 1; const ACTION_LEFT_CLICK = 2; const ACTION_LEAVE_VEHICLE = 3; const ACTION_MOUSEOVER = 4; public $action; public $eid; public $target; /** * */ public function decode(){ $this->action = $this->getByte(); $this->target = $this->getEntityId(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->action); $this->putEntityId($this->target); } /** * @return PacketName|string */ public function getName(){ return "InteractPacket"; } } class InventoryActionPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::INVENTORY_ACTION_PACKET; const ACTION_GIVE_ITEM = 0; const ACTION_ENCHANT_ITEM = 2; public $actionId; public $item; public $enchantmentId = 0; public $enchantmentLevel = 0; /** * */ public function decode(){ $this->actionId = $this->getUnsignedVarInt(); $this->item = $this->getSlot(); $this->enchantmentId = $this->getVarInt(); $this->enchantmentLevel = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt($this->actionId); $this->putSlot($this->item); $this->putVarInt($this->enchantmentId); $this->putVarInt($this->enchantmentLevel); } } class ItemFrameDropItemPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::ITEM_FRAME_DROP_ITEM_PACKET; public $x; public $y; public $z; //public $item; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); //$this->item = $this->getSlot(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "ItemFrameDropItemPacket"; } } class LevelEventPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::LEVEL_EVENT_PACKET; const EVENT_SOUND_CLICK = 1000; const EVENT_SOUND_CLICK_FAIL = 1001; const EVENT_SOUND_SHOOT = 1002; const EVENT_SOUND_DOOR = 1003; const EVENT_SOUND_FIZZ = 1004; const EVENT_SOUND_IGNITE = 1005; const EVENT_SOUND_GHAST = 1007; const EVENT_SOUND_GHAST_SHOOT = 1008; const EVENT_SOUND_BLAZE_SHOOT = 1009; const EVENT_SOUND_DOOR_BUMP = 1010; const EVENT_SOUND_DOOR_CRASH = 1012; const EVENT_SOUND_ENDERMAN_TELEPORT = 1018; const EVENT_SOUND_ANVIL_BREAK = 1020; //This sound is played on the anvil's final use, NOT when the block is broken. const EVENT_SOUND_ANVIL_USE = 1021; const EVENT_SOUND_ANVIL_FALL = 1022; const EVENT_SOUND_POP = 1030; const EVENT_SOUND_PORTAL = 1032; const EVENT_SOUND_ITEMFRAME_ADD_ITEM = 1040; const EVENT_SOUND_ITEMFRAME_REMOVE = 1041; const EVENT_SOUND_ITEMFRAME_PLACE = 1042; const EVENT_SOUND_ITEMFRAME_REMOVE_ITEM = 1043; const EVENT_SOUND_ITEMFRAME_ROTATE_ITEM = 1044; const EVENT_SOUND_CAMERA = 1050; const EVENT_SOUND_ORB = 1051; const EVENT_PARTICLE_SHOOT = 2000; const EVENT_PARTICLE_DESTROY = 2001; const EVENT_PARTICLE_SPLASH = 2002; //This is actually the splash potion sound with particles const EVENT_PARTICLE_EYE_DESPAWN = 2003; const EVENT_PARTICLE_SPAWN = 2004; const EVENT_GUARDIAN_CURSE = 2006; const EVENT_PARTICLE_BLOCK_FORCE_FIELD = 2008; const EVENT_PARTICLE_PUNCH_BLOCK = 2014; const EVENT_START_RAIN = 3001; const EVENT_START_THUNDER = 3002; const EVENT_STOP_RAIN = 3003; const EVENT_STOP_THUNDER = 3004; const EVENT_REDSTONE_TRIGGER = 3500; const EVENT_CAULDRON_EXPLODE = 3501; const EVENT_CAULDRON_DYE_ARMOR = 3502; const EVENT_CAULDRON_CLEAN_ARMOR = 3503; const EVENT_CAULDRON_FILL_POTION = 3504; const EVENT_CAULDRON_TAKE_POTION = 3505; const EVENT_CAULDRON_FILL_WATER = 3506; const EVENT_CAULDRON_TAKE_WATER = 3507; const EVENT_CAULDRON_ADD_DYE = 3508; const EVENT_SET_DATA = 4000; const EVENT_PLAYERS_SLEEPING = 9800; const EVENT_ADD_PARTICLE_MASK = 0x4000; const EVENT_BLOCK_START_BREAK = 3600; const EVENT_BLOCK_STOP_BREAK = 3601; public $evid; public $x = 0; //Weather effects don't have coordinates public $y = 0; public $z = 0; public $data; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->evid); $this->putVector3f($this->x, $this->y, $this->z); $this->putVarInt($this->data); } } class LevelSoundEventPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::LEVEL_SOUND_EVENT_PACKET; const SOUND_ITEM_USE_ON = 0; const SOUND_HIT = 1; const SOUND_STEP = 2; const SOUND_JUMP = 3; const SOUND_BREAK = 4; const SOUND_PLACE = 5; const SOUND_HEAVY_STEP = 6; const SOUND_GALLOP = 7; const SOUND_FALL = 8; const SOUND_AMBIENT = 9; const SOUND_AMBIENT_BABY = 10; const SOUND_AMBIENT_IN_WATER = 11; const SOUND_BREATHE = 12; const SOUND_DEATH = 13; const SOUND_DEATH_IN_WATER = 14; const SOUND_DEATH_TO_ZOMBIE = 15; const SOUND_HURT = 16; const SOUND_HURT_IN_WATER = 17; const SOUND_MAD = 18; const SOUND_BOOST = 19; const SOUND_BOW = 20; const SOUND_SQUISH_BIG = 21; const SOUND_SQUISH_SMALL = 22; const SOUND_FALL_BIG = 23; const SOUND_FALL_SMALL = 24; const SOUND_SPLASH = 25; const SOUND_FIZZ = 26; const SOUND_FLAP = 27; const SOUND_SWIM = 28; const SOUND_DRINK = 29; const SOUND_EAT = 30; const SOUND_TAKEOFF = 31; const SOUND_SHAKE = 32; const SOUND_PLOP = 33; const SOUND_LAND = 34; const SOUND_SADDLE = 35; const SOUND_ARMOR = 36; const SOUND_ADD_CHEST = 37; const SOUND_THROW = 38; const SOUND_ATTACK = 39; const SOUND_ATTACK_NODAMAGE = 40; const SOUND_WARN = 41; const SOUND_SHEAR = 42; const SOUND_MILK = 43; const SOUND_THUNDER = 44; const SOUND_EXPLODE = 45; const SOUND_FIRE = 46; const SOUND_IGNITE = 47; const SOUND_FUSE = 48; const SOUND_STARE = 49; const SOUND_SPAWN = 50; const SOUND_SHOOT = 51; const SOUND_BREAK_BLOCK = 52; const SOUND_REMEDY = 53; const SOUND_UNFECT = 54; const SOUND_LEVELUP = 55; const SOUND_BOW_HIT = 56; const SOUND_BULLET_HIT = 57; const SOUND_EXTINGUISH_FIRE = 58; const SOUND_ITEM_FIZZ = 59; const SOUND_CHEST_OPEN = 60; const SOUND_CHEST_CLOSED = 61; const SOUND_SHULKERBOX_OPEN = 62; const SOUND_SHULKERBOX_CLOSED = 63; const SOUND_POWER_ON = 64; const SOUND_POWER_OFF = 65; const SOUND_ATTACH = 66; const SOUND_DETACH = 67; const SOUND_DENY = 68; const SOUND_TRIPOD = 69; const SOUND_POP = 70; const SOUND_DROP_SLOT = 71; const SOUND_NOTE = 72; const SOUND_THORNS = 73; const SOUND_PISTON_IN = 74; const SOUND_PISTON_OUT = 75; const SOUND_PORTAL = 76; const SOUND_WATER = 77; const SOUND_LAVA_POP = 78; const SOUND_LAVA = 79; const SOUND_BURP = 80; const SOUND_BUCKET_FILL_WATER = 81; const SOUND_BUCKET_FILL_LAVA = 82; const SOUND_BUCKET_EMPTY_WATER = 83; const SOUND_BUCKET_EMPTY_LAVA = 84; const SOUND_GUARDIAN_FLOP = 85; const SOUND_ELDERGUARDIAN_CURSE = 86; const SOUND_MOB_WARNING = 87; const SOUND_MOB_WARNING_BABY = 88; const SOUND_TELEPORT = 89; const SOUND_SHULKER_OPEN = 90; const SOUND_SHULKER_CLOSE = 91; const SOUND_HAGGLE = 92; const SOUND_HAGGLE_YES = 93; const SOUND_HAGGLE_NO = 94; const SOUND_HAGGLE_IDLE = 95; const SOUND_CHORUSGROW = 96; const SOUND_CHORUSDEATH = 97; const SOUND_GLASS = 98; const SOUND_CAST_SPELL = 99; const SOUND_PREPARE_ATTACK = 100; const SOUND_PREPARE_SUMMON = 101; const SOUND_PREPARE_WOLOLO = 102; const SOUND_FANG = 103; const SOUND_CHARGE = 104; const SOUND_CAMERA_TAKE_PICTURE = 105; const SOUND_DEFAULT = 106; const SOUND_UNDEFINED = 107; public $sound; public $x; public $y; public $z; public $extraData = -1; public $pitch = 1; public $unknownBool = false; public $unknownBool2 = false; /** * */ public function decode(){ $this->sound = $this->getByte(); $this->getVector3f($this->x, $this->y, $this->z); $this->extraData = $this->getVarInt(); $this->pitch = $this->getVarInt(); $this->unknownBool = $this->getBool(); $this->unknownBool2 = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->sound); $this->putVector3f($this->x, $this->y, $this->z); $this->putVarInt($this->extraData); $this->putVarInt($this->pitch); $this->putBool($this->unknownBool); $this->putBool($this->unknownBool2); } /** * @return PacketName|string */ public function getName(){ return "LevelSoundEventPacket"; } } class LoginPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::LOGIN_PACKET; const MOJANG_PUBKEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V"; const EDITION_POCKET = 0; public $username; public $protocol; public $gameEdition; public $clientUUID; public $clientId; public $identityPublicKey; public $serverAddress; public $skinId = null; public $skin = null; public $clientData = []; public $deviceModel; public $deviceOS; /** * */ public function decode(){ $this->protocol = $this->getInt(); if(!in_array($this->protocol, ProtocolInfo::ACCEPTED_PROTOCOLS)){ $this->buffer = null; return; } $this->gameEdition = $this->getByte(); $this->setBuffer($this->getString(), 0); $time = time(); $chainData = json_decode($this->get($this->getLInt()))->{"chain"}; // Start with the trusted one $chainKey = self::MOJANG_PUBKEY; while(!empty($chainData)){ foreach($chainData as $index => $chain){ list($verified, $webtoken) = $this->decodeToken($chain, $chainKey); if(isset($webtoken["extraData"])){ if(isset($webtoken["extraData"]["displayName"])){ $this->username = $webtoken["extraData"]["displayName"]; } if(isset($webtoken["extraData"]["identity"])){ $this->clientUUID = $webtoken["extraData"]["identity"]; } } if($verified){ $verified = isset($webtoken["nbf"]) && $webtoken["nbf"] <= $time && isset($webtoken["exp"]) && $webtoken["exp"] > $time; } if($verified and isset($webtoken["identityPublicKey"])){ // Looped key chain. #blamemojang if($webtoken["identityPublicKey"] != self::MOJANG_PUBKEY) $chainKey = $webtoken["identityPublicKey"]; break; }elseif($chainKey === null){ // We have already gave up break; } } if(!$verified && $chainKey !== null){ $chainKey = null; }else{ unset($chainData[$index]); } } list($verified, $this->clientData) = $this->decodeToken($this->get($this->getLInt()), $chainKey); $this->clientId = $this->clientData["ClientRandomId"] ?? null; $this->serverAddress = $this->clientData["ServerAddress"] ?? null; $this->skinId = $this->clientData["SkinId"] ?? null; if(isset($this->clientData["SkinData"])){ $this->skin = base64_decode($this->clientData["SkinData"]); } if(isset($this->clientData["DeviceModel"])){ $this->deviceModel = $this->clientData["DeviceModel"]; } if(isset($this->clientData["DeviceOS"])){ $this->deviceOS = $this->clientData["DeviceOS"]; } if($verified){ $this->identityPublicKey = $chainKey; } } /** * */ public function encode(){ } /** * @param $token * @param $key * * @return array */ public function decodeToken($token, $key){ $tokens = explode(".", $token); list($headB64, $payloadB64, $sigB64) = $tokens; if($key !== null and extension_loaded("openssl")){ $sig = base64_decode(strtr($sigB64, '-_', '+/'), true); $rawLen = 48; // ES384 for($i = $rawLen; $i > 0 and $sig[$rawLen - $i] == chr(0); $i--){ } $j = $i + (ord($sig[$rawLen - $i]) >= 128 ? 1 : 0); for($k = $rawLen; $k > 0 and $sig[2 * $rawLen - $k] == chr(0); $k--){ } $l = $k + (ord($sig[2 * $rawLen - $k]) >= 128 ? 1 : 0); $len = 2 + $j + 2 + $l; $derSig = chr(48); if($len > 255){ throw new \RuntimeException("Invalid signature format"); }elseif($len >= 128){ $derSig .= chr(81); } $derSig .= chr($len) . chr(2) . chr($j); $derSig .= str_repeat(chr(0), $j - $i) . substr($sig, $rawLen - $i, $i); $derSig .= chr(2) . chr($l); $derSig .= str_repeat(chr(0), $l - $k) . substr($sig, 2 * $rawLen - $k, $k); $verified = openssl_verify($headB64 . "." . $payloadB64, $derSig, "-----BEGIN PUBLIC KEY-----\n" . wordwrap($key, 64, "\n", true) . "\n-----END PUBLIC KEY-----\n", OPENSSL_ALGO_SHA384) === 1; }else{ $verified = false; } return [$verified, json_decode(base64_decode($payloadB64), true)]; } } uuid = $this->getEntityId(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->uuid); } } class MobArmorEquipmentPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::MOB_ARMOR_EQUIPMENT_PACKET; public $eid; public $slots = []; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->slots[0] = $this->getSlot(); $this->slots[1] = $this->getSlot(); $this->slots[2] = $this->getSlot(); $this->slots[3] = $this->getSlot(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putSlot($this->slots[0]); $this->putSlot($this->slots[1]); $this->putSlot($this->slots[2]); $this->putSlot($this->slots[3]); } } class MobEffectPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::MOB_EFFECT_PACKET; const EVENT_ADD = 1; const EVENT_MODIFY = 2; const EVENT_REMOVE = 3; public $eid; public $eventId; public $effectId; public $amplifier; public $particles = true; public $duration; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putByte($this->eventId); $this->putVarInt($this->effectId); $this->putVarInt($this->amplifier); $this->putBool($this->particles); $this->putVarInt($this->duration); } /** * @return PacketName|string */ public function getName(){ return "MobEffectPacket"; } } class MobEquipmentPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::MOB_EQUIPMENT_PACKET; public $eid; public $item; public $slot; public $selectedSlot; public $windowId; /** * */ public function decode(){ $this->eid = $this->getEntityId(); //EntityRuntimeID $this->item = $this->getSlot(); $this->slot = $this->getByte(); $this->selectedSlot = $this->getByte(); $this->windowId = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityRuntimeID $this->putSlot($this->item); $this->putByte($this->slot); $this->putByte($this->selectedSlot); $this->putByte($this->windowId); } } class MoveEntityPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::MOVE_ENTITY_PACKET; public $eid; public $x; public $y; public $z; public $yaw; public $headYaw; public $pitch; public $byte1; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->getVector3f($this->x, $this->y, $this->z); $this->pitch = $this->getByte() * (360.0 / 256); $this->yaw = $this->getByte() * (360.0 / 256); $this->headYaw = $this->getByte() * (360.0 / 256); $this->byte1 = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVector3f($this->x, $this->y, $this->z); $this->putByte($this->pitch / (360.0 / 256)); $this->putByte($this->yaw / (360.0 / 256)); $this->putByte($this->headYaw / (360.0 / 256)); $this->putByte($this->byte1); } } class MovePlayerPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::MOVE_PLAYER_PACKET; const MODE_NORMAL = 0; const MODE_RESET = 1; const MODE_ROTATION = 2; public $eid; public $x; public $y; public $z; public $yaw; public $bodyYaw; public $pitch; public $mode = self::MODE_NORMAL; public $onGround; public $eid2; /** * @return $this */ public function clean(){ $this->teleport = false; return parent::clean(); } /** * */ public function decode(){ $this->eid = $this->getEntityId(); //EntityRuntimeID $this->getVector3f($this->x, $this->y, $this->z); $this->pitch = $this->getLFloat(); $this->yaw = $this->getLFloat(); $this->bodyYaw = $this->getLFloat(); $this->mode = $this->getByte(); $this->onGround = $this->getBool(); $this->eid2 = $this->getEntityId(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityRuntimeID $this->putVector3f($this->x, $this->y, $this->z); $this->putLFloat($this->pitch); $this->putLFloat($this->yaw); $this->putLFloat($this->bodyYaw); //TODO $this->putByte($this->mode); $this->putBool($this->onGround); $this->putEntityId($this->eid2); //EntityRuntimeID } } class PlaySoundPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::PLAY_SOUND_PACKET; public $sound; public $x; public $y; public $z; public $volume; public $float; /** * */ public function decode(){ $this->sound = $this->getString(); $this->getBlockPos($this->x, $this->y, $this->z); $this->volume = $this->getFloat(); $this->float = $this->getFloat(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->sound); $this->putBlockPos($this->x, $this->y, $this->z); $this->putFloat($this->volume); $this->putFloat($this->float); } /** * @return PacketName|string */ public function getName(){ return "PlaySoundPacket"; } } class PlayStatusPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::PLAY_STATUS_PACKET; const LOGIN_SUCCESS = 0; const LOGIN_FAILED_CLIENT = 1; const LOGIN_FAILED_SERVER = 2; const PLAYER_SPAWN = 3; const LOGIN_FAILED_INVALID_TENANT = 4; const LOGIN_FAILED_VANILLA_EDU = 5; const LOGIN_FAILED_EDU_VANILLA = 6; public $status; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putInt($this->status); } } class PlayerActionPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::PLAYER_ACTION_PACKET; const ACTION_START_BREAK = 0; const ACTION_ABORT_BREAK = 1; const ACTION_STOP_BREAK = 2; const ACTION_RELEASE_ITEM = 5; const ACTION_STOP_SLEEPING = 6; const ACTION_SPAWN_SAME_DIMENSION = 7; const ACTION_JUMP = 8; const ACTION_START_SPRINT = 9; const ACTION_STOP_SPRINT = 10; const ACTION_START_SNEAK = 11; const ACTION_STOP_SNEAK = 12; const ACTION_SPAWN_OVERWORLD = 13; const ACTION_SPAWN_NETHER = 14; const ACTION_START_GLIDE = 15; const ACTION_STOP_GLIDE = 16; const ACTION_BUILD_DENIED = 17; const ACTION_CONTINUE_BREAK = 18; public $eid; public $action; public $x; public $y; public $z; public $face; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->action = $this->getVarInt(); $this->getBlockCoords($this->x, $this->y, $this->z); $this->face = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVarInt($this->action); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->face); } } class PlayerInputPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::PLAYER_INPUT_PACKET; public $motionX; public $motionY; public $unknownBool1; public $unknownBool2; /** * */ public function decode(){ $this->motionX = $this->getLFloat(); $this->motionY = $this->getLFloat(); $this->unknownBool1 = $this->getBool(); $this->unknownBool2 = $this->getBool(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "PlayerInputPacket"; } } class PlayerListPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::PLAYER_LIST_PACKET; const TYPE_ADD = 0; const TYPE_REMOVE = 1; //REMOVE: UUID, ADD: UUID, entity id, name, skinId, skin /** @var array[] */ public $entries = []; public $type; /** * @return $this */ public function clean(){ $this->entries = []; return parent::clean(); } /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putByte($this->type); $this->putUnsignedVarInt(count($this->entries)); foreach($this->entries as $d){ if($this->type === self::TYPE_ADD){ $this->putUUID($d[0]); $this->putEntityId($d[1]); $this->putString($d[2]); $this->putString($d[3]); $this->putString($d[4]); }else{ $this->putUUID($d[0]); } } } /** * @return PacketName|string */ public function getName(){ return "PlayerListPacket"; } } class RemoveBlockPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::REMOVE_BLOCK_PACKET; public $x; public $y; public $z; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "RemoveBlockPacket"; } } class RemoveEntityPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::REMOVE_ENTITY_PACKET; public $eid; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); } } class ReplaceItemInSlotPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::REPLACE_ITEM_IN_SLOT_PACKET; public $item; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putSlot($this->item); } }radius = $this->getVarInt(); } /** * */ public function encode(){ } } class ResourcePackChunkDataPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_CHUNK_DATA_PACKET; public $packId; public $chunkIndex; public $progress; public $data; /** * */ public function decode(){ $this->packId = $this->getString(); $this->chunkIndex = $this->getLInt(); $this->progress = $this->getLLong(); $this->data = $this->get($this->getLInt()); } /** * */ public function encode(){ $this->reset(); $this->putString($this->packId); $this->putLInt($this->chunkIndex); $this->putLLong($this->progress); $this->putLInt(strlen($this->data)); $this->put($this->data); } } class ResourcePackChunkRequestPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_CHUNK_REQUEST_PACKET; public $packId; public $chunkIndex; /** * */ public function decode(){ $this->packId = $this->getString(); $this->chunkIndex = $this->getLInt(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->packId); $this->putLInt($this->chunkIndex); } } class ResourcePackClientResponsePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_CLIENT_RESPONSE_PACKET; const STATUS_REFUSED = 1; const STATUS_SEND_PACKS = 2; const STATUS_HAVE_ALL_PACKS = 3; const STATUS_COMPLETED = 4; public $status; public $packIds = []; /** * */ public function decode(){ $this->status = $this->getByte(); $entryCount = $this->getLShort(); while($entryCount-- > 0){ $this->packIds[] = $this->getString(); } } /** * */ public function encode(){ $this->reset(); $this->putByte($this->status); $this->putLShort(count($this->packIds)); foreach($this->packIds as $id){ $this->putString($id); } } } class ResourcePackDataInfoPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_DATA_INFO_PACKET; public $packId; public $maxChunkSize; public $chunkCount; public $compressedPackSize; public $sha256; /** * */ public function decode(){ $this->packId = $this->getString(); $this->maxChunkSize = $this->getLInt(); $this->chunkCount = $this->getLInt(); $this->compressedPackSize = $this->getLLong(); $this->sha256 = $this->getString(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->packId); $this->putLInt($this->maxChunkSize); $this->putLInt($this->chunkCount); $this->putLLong($this->compressedPackSize); $this->putString($this->sha256); } } use pocketmine\resourcepacks\ResourcePack; class ResourcePackStackPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_STACK_PACKET; public $mustAccept = false; /** @var ResourcePack[] */ public $behaviorPackStack = []; /** @var ResourcePack[] */ public $resourcePackStack = []; /** * */ public function decode(){ /*$this->mustAccept = $this->getBool(); $behaviorPackCount = $this->getLShort(); while($behaviorPackCount-- > 0){ $packId = $this->getString(); $version = $this->getString(); $this->behaviorPackStack[] = new ResourcePackInfoEntry($packId, $version); } $resourcePackCount = $this->getLShort(); while($resourcePackCount-- > 0){ $packId = $this->getString(); $version = $this->getString(); $this->resourcePackStack[] = new ResourcePackInfoEntry($packId, $version); }*/ } /** * */ public function encode(){ $this->reset(); $this->putBool($this->mustAccept); $this->putUnsignedVarInt(count($this->behaviorPackStack)); foreach($this->behaviorPackStack as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); } $this->putUnsignedVarInt(count($this->resourcePackStack)); foreach($this->resourcePackStack as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); } } } use pocketmine\resourcepacks\ResourcePackInfoEntry; class ResourcePacksInfoPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RESOURCE_PACKS_INFO_PACKET; public $mustAccept = false; //force client to use selected resource packs /** @var ResourcePackInfoEntry */ public $behaviorPackEntries = []; /** @var ResourcePackInfoEntry */ public $resourcePackEntries = []; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBool($this->mustAccept); $this->putLShort(count($this->behaviorPackEntries)); foreach($this->behaviorPackEntries as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); $this->putLLong($entry->getPackSize()); } $this->putLShort(count($this->resourcePackEntries)); foreach($this->resourcePackEntries as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); $this->putLLong($entry->getPackSize()); } } } class RespawnPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RESPAWN_PACKET; public $x; public $y; public $z; /** * */ public function decode(){ $this->x = $this->getLFloat(); $this->y = $this->getLFloat(); $this->z = $this->getLFloat(); } /** * */ public function encode(){ $this->reset(); $this->putLFloat($this->x); $this->putLFloat($this->y); $this->putLFloat($this->z); } /** * @return PacketName|string */ public function getName(){ return "RespawnPacket"; } } class RiderJumpPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::RIDER_JUMP_PACKET; public $unknown; /** * */ public function decode(){ $this->unknown = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->unknown); } } class ServerToClientHandshakePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SERVER_TO_CLIENT_HANDSHAKE_PACKET; public $publicKey; public $serverToken; /** * @return bool */ public function canBeSentBeforeLogin() : bool{ return true; } /** * */ public function decode(){ $this->publicKey = $this->getString(); $this->serverToken = $this->getString(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->publicKey); $this->putString($this->serverToken); } } class SetCommandsEnabledPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_COMMANDS_ENABLED_PACKET; public $enabled; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBool($this->enabled); } /** * @return PacketName|string */ public function getName(){ return "SetCommandsEnabledPacket"; } } class SetDifficultyPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_DIFFICULTY_PACKET; public $difficulty; /** * */ public function decode(){ $this->difficulty = $this->getUnsignedVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt($this->difficulty); } /** * @return PacketName|string */ public function getName(){ return "SetDifficultyPacket"; } }reset(); $this->putEntityId($this->eid); $this->putEntityMetadata($this->metadata); } /** * @return PacketName|string */ public function getName(){ return "SetEntityDataPacket"; } } class SetEntityLinkPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_ENTITY_LINK_PACKET; const TYPE_REMOVE = 0; const TYPE_RIDE = 1; const TYPE_PASSENGER = 2; public $from; public $to; public $type; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->from); $this->putEntityId($this->to); $this->putByte($this->type); } /** * @return PacketName|string */ public function getName(){ return "SetEntityLinkPacket"; } } class SetEntityMotionPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_ENTITY_MOTION_PACKET; public $eid; public $motionX; public $motionY; public $motionZ; /** * @return $this */ public function clean(){ $this->entities = []; return parent::clean(); } /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVector3f($this->motionX, $this->motionY, $this->motionZ); } /** * @return PacketName|string */ public function getName(){ return "SetEntityMotionPacket"; } } class SetHealthPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_HEALTH_PACKET; public $health; /** * */ public function decode(){ $this->health = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->health); } /** * @return PacketName|string */ public function getName(){ return "SetHealthPacket"; } } class SetPlayerGameTypePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_PLAYER_GAME_TYPE_PACKET; public $gamemode; /** * */ public function decode(){ $this->gamemode = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->gamemode); } /** * @return PacketName|string */ public function getName(){ return "SetPlayerGameTypePacket"; } } class SetSpawnPositionPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_SPAWN_POSITION_PACKET; public $unknown; public $x; public $y; public $z; public $unknownBool; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->unknown); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putBool($this->unknownBool); } /** * @return PacketName|string */ public function getName(){ return "SetSpawnPositionPacket"; } } class SetTimePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_TIME_PACKET; public $time; public $started = true; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->time); $this->putBool($this->started); } /** * @return PacketName|string */ public function getName(){ return "SetTimePacket"; } } class SetTitlePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SET_TITLE_PACKET; const TYPE_CLEAR = 0; const TYPE_RESET = 1; const TYPE_TITLE = 2; const TYPE_SUB_TITLE = 3; const TYPE_ACTION_BAR = 4; const TYPE_TIMES = 5; public $type; public $title; public $fadeInDuration; public $duration; public $fadeOutDuration; /** * */ public function decode(){ $this->type = $this->getVarInt(); $this->title = $this->getString(); $this->fadeInDuration = $this->getVarInt(); $this->duration = $this->getVarInt(); $this->fadeOutDuration = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->type); $this->putString($this->title); $this->putVarInt($this->fadeInDuration); $this->putVarInt($this->duration); $this->putVarInt($this->fadeOutDuration); } /** * @return PacketName|string */ public function getName(){ return "SetTitlePacket"; } } class ShowCreditsPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SHOW_CREDITS_PACKET; public $eid; public $type; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVarInt($this->type); } /** * @return PacketName|string */ public function getName(){ return "ShowCreditsPacket"; } } class SpawnExperienceOrbPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::SPAWN_EXPERIENCE_ORB_PACKET; public $x; public $y; public $z; public $amount; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVector3f($this->x, $this->y, $this->z); $this->putVarInt($this->amount); } /** * @return PacketName|string */ public function getName(){ return "SpawnExperienceOrbPacket"; } } class StartGamePacket extends DataPacket { const NETWORK_ID = ProtocolInfo::START_GAME_PACKET; public $entityUniqueId; public $entityRuntimeId; public $playerGamemode; public $x; public $y; public $z; public $pitch; public $yaw; public $seed; public $dimension; public $generator = 1; //default infinite - 0 old, 1 infinite, 2 flat public $worldGamemode; public $difficulty; public $spawnX; public $spawnY; public $spawnZ; public $hasAchievementsDisabled = 1; public $dayCycleStopTime = -1; //-1 = not stopped, any positive value = stopped at that time public $eduMode = 0; public $rainLevel; public $lightningLevel; public $commandsEnabled; public $isTexturePacksRequired = 0; public $levelId = ""; public $worldName; public $premiumWorldTemplateId = ""; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityUniqueId); //EntityUniqueID $this->putEntityId($this->entityRuntimeId); //EntityRuntimeID $this->putVarInt($this->playerGamemode); //client gamemode, other field is world gamemode $this->putVector3f($this->x, $this->y, $this->z); $this->putLFloat($this->pitch); $this->putLFloat($this->yaw); $this->putVarInt($this->seed); $this->putVarInt($this->dimension); $this->putVarInt($this->generator); $this->putVarInt($this->worldGamemode); $this->putVarInt($this->difficulty); $this->putBlockCoords($this->spawnX, $this->spawnY, $this->spawnZ); $this->putBool($this->hasAchievementsDisabled); $this->putVarInt($this->dayCycleStopTime); $this->putBool($this->eduMode); $this->putLFloat($this->rainLevel); $this->putLFloat($this->lightningLevel); $this->putBool($this->commandsEnabled); $this->putBool($this->isTexturePacksRequired); $this->putUnsignedVarInt(0); //TODO: gamerules $this->putString($this->levelId); $this->putString($this->worldName); $this->putString($this->premiumWorldTemplateId); } } class StopSoundPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::STOP_SOUND_PACKET; public $sound; public $stopAll; /** * */ public function decode(){ $this->sound = $this->getString(); $this->stopAll = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->sound); $this->putBool($this->stopAll); } } class TakeItemEntityPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::TAKE_ITEM_ENTITY_PACKET; public $target; public $eid; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->target); $this->putEntityId($this->eid); } /** * @return PacketName|string */ public function getName(){ return "TakeItemEntityPacket"; } } class TextPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::TEXT_PACKET; const TYPE_RAW = 0; const TYPE_CHAT = 1; const TYPE_TRANSLATION = 2; const TYPE_POPUP = 3; const TYPE_TIP = 4; const TYPE_SYSTEM = 5; const TYPE_WHISPER = 6; public $type; public $source; public $message; public $parameters = []; /** * */ public function decode(){ $this->type = $this->getByte(); switch($this->type){ case self::TYPE_POPUP: case self::TYPE_CHAT: /** @noinspection PhpMissingBreakStatementInspection */ case self::TYPE_WHISPER: $this->source = $this->getString(); case self::TYPE_RAW: case self::TYPE_TIP: case self::TYPE_SYSTEM: $this->message = $this->getString(); break; case self::TYPE_TRANSLATION: $this->message = $this->getString(); $count = $this->getUnsignedVarInt(); for($i = 0; $i < $count; ++$i){ $this->parameters[] = $this->getString(); } } } /** * */ public function encode(){ $this->reset(); $this->putByte($this->type); switch($this->type){ case self::TYPE_POPUP: case self::TYPE_CHAT: /** @noinspection PhpMissingBreakStatementInspection */ case self::TYPE_WHISPER: $this->putString($this->source); case self::TYPE_RAW: case self::TYPE_TIP: case self::TYPE_SYSTEM: $this->putString($this->message); break; case self::TYPE_TRANSLATION: $this->putString($this->message); $this->putUnsignedVarInt(count($this->parameters)); foreach($this->parameters as $p){ $this->putString($p); } } } /** * @return PacketName|string */ public function getName(){ return "TextPacket"; } } reset(); $this->putString($this->address); $this->putLShort($this->port); } } use pocketmine\entity\Attribute; class UpdateAttributesPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::UPDATE_ATTRIBUTES_PACKET; public $entityId; /** @var Attribute[] */ public $entries = []; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityId); $this->putUnsignedVarInt(count($this->entries)); foreach($this->entries as $entry){ $this->putLFloat($entry->getMinValue()); $this->putLFloat($entry->getMaxValue()); $this->putLFloat($entry->getValue()); $this->putLFloat($entry->getDefaultValue()); $this->putString($entry->getName()); } } /** * @return PacketName|string */ public function getName(){ return "UpdateAttributesPacket"; } } class UpdateBlockPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::UPDATE_BLOCK_PACKET; const FLAG_NONE = 0b0000; const FLAG_NEIGHBORS = 0b0001; const FLAG_NETWORK = 0b0010; const FLAG_NOGRAPHIC = 0b0100; const FLAG_PRIORITY = 0b1000; const FLAG_ALL = (self::FLAG_NEIGHBORS | self::FLAG_NETWORK); const FLAG_ALL_PRIORITY = (self::FLAG_ALL | self::FLAG_PRIORITY); public $x; public $z; public $y; public $blockId; public $blockData; public $flags; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putUnsignedVarInt($this->blockId); $this->putUnsignedVarInt(($this->flags << 4) | $this->blockData); } /** * @return PacketName|string */ public function getName(){ return "UpdateBlockPacket"; } }byte1 = $this->getByte(); $this->byte2 = $this->getByte(); $this->varint1 = $this->getVarInt(); $this->varint2 = $this->getVarInt(); $this->isWilling = $this->getBool(); $this->traderEid = $this->getEntityId(); $this->playerEid = $this->getEntityId(); $this->displayName = $this->getString(); $this->offers = $this->get(true); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->byte1); $this->putByte($this->byte2); $this->putVarInt($this->varint1); $this->putVarInt($this->varint2); $this->putBool($this->isWilling); $this->putEntityId($this->traderEid); $this->putEntityId($this->playerEid); $this->putString($this->displayName); $this->put($this->offers); } } class UseItemPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::USE_ITEM_PACKET; public $x; public $y; public $z; public $blockId; public $face; public $item; public $fx; public $fy; public $fz; public $posX; public $posY; public $posZ; public $slot; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); $this->blockId = $this->getUnsignedVarInt(); $this->face = $this->getVarInt(); $this->getVector3f($this->fx, $this->fy, $this->fz); $this->getVector3f($this->posX, $this->posY, $this->posZ); $this->slot = $this->getVarInt(); $this->item = $this->getSlot(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "UseItemPacket"; } } #ifndef COMPILE use pocketmine\entity\Attribute; #endif class AddEntityPacket extends DataPacket { const NETWORK_ID = Info::ADD_ENTITY_PACKET; public $eid; public $type; public $x; public $y; public $z; public $speedX; public $speedY; public $speedZ; public $yaw; public $pitch; /** @var Attribute[] */ public $attributes = []; public $metadata = []; public $links = []; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityUniqueID - TODO: verify this $this->putEntityId($this->eid); $this->putUnsignedVarInt($this->type); $this->putVector3f($this->x, $this->y, $this->z); $this->putVector3f($this->speedX, $this->speedY, $this->speedZ); $this->putLFloat($this->pitch * (256 / 360)); $this->putLFloat($this->yaw * (256 / 360)); $this->putUnsignedVarInt(count($this->attributes)); foreach($this->attributes as $entry){ $this->putString($entry->getName()); $this->putLFloat($entry->getMinValue()); $this->putLFloat($entry->getValue()); $this->putLFloat($entry->getMaxValue()); } $this->putEntityMetadata($this->metadata); $this->putUnsignedVarInt(count($this->links)); foreach($this->links as $link){ $this->putEntityId($link[0]); $this->putEntityId($link[1]); $this->putByte($link[2]); } } /** * @return AddEntityPacket|string */ public function getName(){ return "AddEntityPacket"; } } class AddHangingEntityPacket extends DataPacket { const NETWORK_ID = Info::ADD_HANGING_ENTITY_PACKET; public $entityUniqueId; public $entityRuntimeId; public $x; public $y; public $z; public $unknown; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityUniqueId); $this->putEntityId($this->entityRuntimeId); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->unknown); } /** * @return AddHangingEntityPacket|string */ public function getName(){ return "AddHangingEntityPacket"; } } class AddItemEntityPacket extends DataPacket { const NETWORK_ID = Info::ADD_ITEM_ENTITY_PACKET; public $eid; public $item; public $x; public $y; public $z; public $speedX; public $speedY; public $speedZ; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityUniqueID $this->putEntityId($this->eid); //EntityRuntimeID $this->putSlot($this->item); $this->putVector3f($this->x, $this->y, $this->z); $this->putVector3f($this->speedX, $this->speedY, $this->speedZ); } /** * @return AddItemEntityPacket|string */ public function getName(){ return "AddItemEntityPacket"; } } class AddItemPacket extends DataPacket { const NETWORK_ID = Info::ADD_ITEM_PACKET; public $item; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putSlot($this->item); } /** * @return AddItemPacket|string */ public function getName(){ return "AddItemPacket"; } } class AddPaintingPacket extends DataPacket { const NETWORK_ID = Info::ADD_PAINTING_PACKET; public $eid; public $x; public $y; public $z; public $direction; public $title; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityUniqueID $this->putEntityId($this->eid); //EntityRuntimeID $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->direction); $this->putString($this->title); } /** * @return string */ public function getName(){ return "AddPaintingPacket"; } } reset(); $this->putUUID($this->uuid); $this->putString($this->username); $this->putEntityId($this->eid); //EntityUniqueID $this->putEntityId($this->eid); //EntityRuntimeID $this->putVector3f($this->x, $this->y, $this->z); $this->putVector3f($this->speedX, $this->speedY, $this->speedZ); $this->putLFloat($this->pitch); $this->putLFloat($this->headYaw ?? $this->yaw); $this->putLFloat($this->yaw); $this->putSlot($this->item); $this->putEntityMetadata($this->metadata); } /** * @return PacketName|string */ public function getName(){ return "AddPlayerPacket"; } } class AdventureSettingsPacket extends DataPacket { const NETWORK_ID = Info::ADVENTURE_SETTINGS_PACKET; const PERMISSION_NORMAL = 0; const PERMISSION_OPERATOR = 1; const PERMISSION_HOST = 2; const PERMISSION_AUTOMATION = 3; const PERMISSION_ADMIN = 4; public $worldImmutable = false; public $noPvp = false; public $noPvm = false; public $noMvp = false; public $autoJump = false; public $allowFlight = false; public $noClip = false; public $worldBuilder = false; public $isFlying = false; public $muted = false; public $flags = 0; public $userPermission; /** * */ public function decode(){ $this->flags = $this->getUnsignedVarInt(); $this->userPermission = $this->getUnsignedVarInt(); $this->worldImmutable = (bool) ($this->flags & 1); $this->noPvp = (bool) ($this->flags & (1 << 1)); $this->noPvm = (bool) ($this->flags & (1 << 2)); $this->noMvp = (bool) ($this->flags & (1 << 3)); $this->autoJump = (bool) ($this->flags & (1 << 5)); $this->allowFlight = (bool) ($this->flags & (1 << 6)); $this->noClip = (bool) ($this->flags & (1 << 7)); $this->worldBuilder = (bool) ($this->flags & (1 << 8)); $this->isFlying = (bool) ($this->flags & (1 << 9)); $this->muted = (bool) ($this->flags & (1 << 10)); } /** * */ public function encode(){ $this->reset(); $this->flags |= ((int) $this->worldImmutable); $this->flags |= ((int) $this->noPvp) << 1; $this->flags |= ((int) $this->noPvm) << 2; $this->flags |= ((int) $this->noMvp) << 3; $this->flags |= ((int) $this->autoJump) << 5; $this->flags |= ((int) $this->allowFlight) << 6; $this->flags |= ((int) $this->noClip) << 7; $this->flags |= ((int) $this->worldBuilder) << 8; $this->flags |= ((int) $this->isFlying) << 9; $this->flags |= ((int) $this->muted) << 10; $this->putUnsignedVarInt($this->flags); $this->putUnsignedVarInt($this->userPermission); } } class AnimatePacket extends DataPacket { const NETWORK_ID = Info::ANIMATE_PACKET; public $action; public $eid; public $float; /** * */ public function decode(){ $this->action = $this->getVarInt(); $this->eid = $this->getEntityId(); if($this->float & 0x80){ $this->float = $this->getLFloat(); } } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->action); $this->putEntityId($this->eid); if($this->float & 0x80){ $this->putLFloat($this->float); } } } class AvailableCommandsPacket extends DataPacket { const NETWORK_ID = Info::AVAILABLE_COMMANDS_PACKET; public $commands; //JSON-encoded command data public $unknown; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putString($this->commands); $this->putString($this->unknown); } /** * @return PacketName|string */ public function getName(){ return "AvailableCommandsPacket"; } } class BatchPacket extends DataPacket { const NETWORK_ID = 0xfe; public $payload; /** * */ public function decode(){ $this->payload = $this->get(true); } /** * */ public function encode(){ $this->reset(); $this->put($this->payload); } /** * @return PacketName|string */ public function getName(){ return "BatchPacket"; } } class BlockEntityDataPacket extends DataPacket { const NETWORK_ID = Info::BLOCK_ENTITY_DATA_PACKET; public $x; public $y; public $z; public $namedtag; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); $this->namedtag = $this->get(true); } /** * */ public function encode(){ $this->reset(); $this->putBlockCoords($this->x, $this->y, $this->z); $this->put($this->namedtag); } /** * @return PacketName|string */ public function getName(){ return "BlockEntityDataPacket"; } } class BlockEventPacket extends DataPacket { const NETWORK_ID = Info::BLOCK_EVENT_PACKET; public $x; public $y; public $z; public $case1; public $case2; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->case1); $this->putVarInt($this->case2); } /** * @return PacketName|string */ public function getName(){ return "BlockEventPacket"; } } class BlockPickRequestPacket extends DataPacket { const NETWORK_ID = Info::BLOCK_PICK_REQUEST_PACKET; public $x; public $y; public $z; public $unknown; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); $this->unknown = $this->getByte(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "BlockPickRequestPacket"; } } class BossEventPacket extends DataPacket { const NETWORK_ID = Info::BOSS_EVENT_PACKET; public $eid; public $type; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putUnsignedVarInt($this->type); } /** * @return PacketName|string */ public function getName(){ return "BossEventPacket"; } } namespace pocketmine\network\protocol; class CameraPacket extends DataPacket { const NETWORK_ID = Info::CAMERA_PACKET; public $eid; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->eid); $this->putVarInt($this->eid); } /** * @return PacketName|string */ public function getName(){ return "CameraPacket"; } } class ChangeDimensionPacket extends DataPacket { const NETWORK_ID = Info::CHANGE_DIMENSION_PACKET; const DIMENSION_NORMAL = 0; const DIMENSION_NETHER = 1; const DIMENSION_END = 2; public $dimension; public $x; public $y; public $z; public $unknown; //bool public function decode(){ } public function encode(){ $this->reset(); $this->putVarInt($this->dimension); $this->putVector3f($this->x, $this->y, $this->z); $this->putBool($this->unknown); } }reset(); $this->putVarInt($this->radius); } /** * @return PacketName|string */ public function getName(){ return "ChunkRadiusUpdatedPacket"; } } class ClientToServerHandshakePacket extends DataPacket { const NETWORK_ID = Info::CLIENT_TO_SERVER_HANDSHAKE_PACKET; /** * @return bool */ public function canBeSentBeforeLogin() : bool{ return true; } /** * */ public function decode(){ //No payload } /** * */ public function encode(){ $this->reset(); //No payload } } use pocketmine\network\protocol\Info as ProtocolInfo; use pocketmine\utils\Color; class ClientboundMapItemDataPacket extends DataPacket { const NETWORK_ID = ProtocolInfo::CLIENTBOUND_MAP_ITEM_DATA_PACKET; const BITFLAG_TEXTURE_UPDATE = 0x02; const BITFLAG_DECORATION_UPDATE = 0x04; const BITFLAG_ENTITY_UPDATE = 0x08; public $mapId; public $type; public $eids = []; public $scale; public $decorations = []; public $width; public $height; public $xOffset = 0; public $yOffset = 0; /** @var Color[][] */ public $colors = []; /** * */ public function decode(){ $this->mapId = $this->getVarInt(); $this->type = $this->getUnsignedVarInt(); if(($this->type & self::BITFLAG_ENTITY_UPDATE) !== 0){ $count = $this->getUnsignedVarInt(); for($i = 0; $i < $count; ++$i){ $this->eids[] = $this->getVarInt(); //entity unique ID, signed var-int } } if(($this->type & (self::BITFLAG_DECORATION_UPDATE | self::BITFLAG_TEXTURE_UPDATE)) !== 0){ //Decoration bitflag or colour bitflag $this->scale = $this->getByte(); } if(($this->type & self::BITFLAG_DECORATION_UPDATE) !== 0){ $count = $this->getUnsignedVarInt(); for($i = 0; $i < $count; ++$i){ $weird = $this->getVarInt(); $this->decorations[$i]["rot"] = $weird & 0x0f; $this->decorations[$i]["img"] = $weird >> 4; $this->decorations[$i]["xOffset"] = $this->getByte(); $this->decorations[$i]["yOffset"] = $this->getByte(); $this->decorations[$i]["label"] = $this->getString(); $this->decorations[$i]["color"] = Color::fromARGB($this->getLInt()); //already BE, don't need to reverse it again } } if(($this->type & self::BITFLAG_TEXTURE_UPDATE) !== 0){ $this->width = $this->getVarInt(); $this->height = $this->getVarInt(); $this->xOffset = $this->getVarInt(); $this->yOffset = $this->getVarInt(); for($y = 0; $y < $this->height; ++$y){ for($x = 0; $x < $this->width; ++$x){ $this->colors[$y][$x] = Color::fromABGR($this->getUnsignedVarInt()); } } } } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->mapId); //entity unique ID, signed var-int $type = 0; if(($eidsCount = count($this->eids)) > 0){ $type |= self::BITFLAG_ENTITY_UPDATE; } if(($decorationCount = count($this->decorations)) > 0){ $type |= self::BITFLAG_DECORATION_UPDATE; } if(count($this->colors) > 0){ $type |= self::BITFLAG_TEXTURE_UPDATE; } $this->putUnsignedVarInt($type); if(($type & self::BITFLAG_ENTITY_UPDATE) !== 0){ //TODO: find out what these are for $this->putUnsignedVarInt($eidsCount); foreach($this->eids as $eid){ $this->putVarInt($eid); } } if(($type & (self::BITFLAG_TEXTURE_UPDATE | self::BITFLAG_DECORATION_UPDATE)) !== 0){ $this->putByte($this->scale); } if(($type & self::BITFLAG_DECORATION_UPDATE) !== 0){ $this->putUnsignedVarInt($decorationCount); foreach($this->decorations as $decoration){ $this->putVarInt(($decoration["rot"] & 0x0f) | ($decoration["img"] << 4)); $this->putByte($decoration["xOffset"]); $this->putByte($decoration["yOffset"]); $this->putString($decoration["label"]); $this->putLInt($decoration["color"]->toARGB()); } } if(($type & self::BITFLAG_TEXTURE_UPDATE) !== 0){ $this->putVarInt($this->width); $this->putVarInt($this->height); $this->putVarInt($this->xOffset); $this->putVarInt($this->yOffset); for($y = 0; $y < $this->height; ++$y){ for($x = 0; $x < $this->width; ++$x){ $this->putUnsignedVarInt($this->colors[$y][$x]->toABGR()); } } } } } class CommandBlockUpdatePacket extends DataPacket { const NETWORK_ID = Info::COMMAND_BLOCK_UPDATE_PACKET; public $isBlock; public $x; public $y; public $z; public $commandBlockMode; public $isRedstoneMode; public $isConditional; public $minecartEid; public $command; public $lastOutput; public $name; public $shouldTrackOutput; /** * */ public function decode(){ $this->isBlock = $this->getBool(); if($this->isBlock){ $this->getBlockPosition($this->x, $this->y, $this->z); $this->commandBlockMode = $this->getUnsignedVarInt(); $this->isRedstoneMode = $this->getBool(); $this->isConditional = $this->getBool(); }else{ //Minecart with command block $this->minecartEid = $this->getEntityRuntimeId(); } $this->command = $this->getString(); $this->lastOutput = $this->getString(); $this->name = $this->getString(); $this->shouldTrackOutput = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putBool($this->isBlock); if($this->isBlock){ $this->putBlockPosition($this->x, $this->y, $this->z); $this->putUnsignedVarInt($this->commandBlockMode); $this->putBool($this->isRedstoneMode); $this->putBool($this->isConditional); }else{ $this->putEntityRuntimeId($this->minecartEid); } $this->putString($this->command); $this->putString($this->lastOutput); $this->putString($this->name); $this->putBool($this->shouldTrackOutput); } } class CommandStepPacket extends DataPacket { const NETWORK_ID = Info::COMMAND_STEP_PACKET; public $command; public $overload; public $uvarint1; public $currentStep; public $done; public $clientId; public $inputJson; public $outputJson; /** * */ public function decode(){ $this->command = $this->getString(); $this->overload = $this->getString(); $this->uvarint1 = $this->getUnsignedVarInt(); $this->currentStep = $this->getUnsignedVarInt(); $this->done = (bool) $this->getByte(); $this->clientId = $this->getUnsignedVarInt(); //TODO: varint64 $this->inputJson = json_decode($this->getString()); $this->outputJson = $this->getString(); $this->get(true); } /** * */ public function encode(){ } } class ContainerClosePacket extends DataPacket { const NETWORK_ID = Info::CONTAINER_CLOSE_PACKET; public $windowid; /** * */ public function decode(){ $this->windowid = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); } /** * @return PacketName|string */ public function getName(){ return "ContainerClosePacket"; } } class ContainerOpenPacket extends DataPacket { const NETWORK_ID = Info::CONTAINER_OPEN_PACKET; public $windowid; public $type; public $x; public $y; public $z; public $entityId = -1; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); $this->putByte($this->type); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putEntityId($this->entityId); } } class ContainerSetContentPacket extends DataPacket { const NETWORK_ID = Info::CONTAINER_SET_CONTENT_PACKET; const SPECIAL_INVENTORY = 0; const SPECIAL_ARMOR = 0x78; const SPECIAL_CREATIVE = 0x79; const SPECIAL_HOTBAR = 0x7a; const SPECIAL_FIXED_INVENTORY = 0x7b; public $windowid; public $targetEid; public $slots = []; public $hotbar = []; /** * @return $this */ public function clean(){ $this->slots = []; $this->hotbar = []; return parent::clean(); } /** * */ public function decode(){ $this->windowid = $this->getUnsignedVarInt(); $this->targetEid = $this->getEntityId(); $count = $this->getUnsignedVarInt(); for($s = 0; $s < $count and !$this->feof(); ++$s){ $this->slots[$s] = $this->getSlot(); } if($this->windowid === self::SPECIAL_INVENTORY){ $count = $this->getUnsignedVarInt(); for($s = 0; $s < $count and !$this->feof(); ++$s){ $this->hotbar[$s] = $this->getVarInt(); } } } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt($this->windowid); $this->putEntityId($this->targetEid); $this->putUnsignedVarInt(count($this->slots)); foreach($this->slots as $slot){ $this->putSlot($slot); } if($this->windowid === self::SPECIAL_INVENTORY and count($this->hotbar) > 0){ $this->putUnsignedVarInt(count($this->hotbar)); foreach($this->hotbar as $slot){ $this->putVarInt($slot); } }else{ $this->putUnsignedVarInt(0); } } /** * @return PacketName|string */ public function getName(){ return "ContainerSetContentPacket"; } } class ContainerSetDataPacket extends DataPacket { const NETWORK_ID = Info::CONTAINER_SET_DATA_PACKET; public $windowid; public $property; public $value; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); $this->putVarInt($this->property); $this->putVarInt($this->value); } /** * @return PacketName|string */ public function getName(){ return "ContainerSetDataPacket"; } } use pocketmine\item\Item; class ContainerSetSlotPacket extends DataPacket { const NETWORK_ID = Info::CONTAINER_SET_SLOT_PACKET; public $windowid; public $slot; /** @var Item */ public $item; public $hotbarSlot; public $unknown; /** * */ public function decode(){ $this->windowid = $this->getByte(); $this->slot = $this->getVarInt(); $this->hotbarSlot = $this->getVarInt(); $this->item = $this->getSlot(); $this->unknown = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->windowid); $this->putVarInt($this->slot); $this->putVarInt($this->hotbarSlot); $this->putSlot($this->item); $this->putByte($this->unknown); } /** * @return PacketName|string */ public function getName(){ return "ContainerSetSlotPacket"; } } use pocketmine\inventory\FurnaceRecipe; use pocketmine\inventory\ShapedRecipe; use pocketmine\inventory\ShapelessRecipe; use pocketmine\item\Item; use pocketmine\utils\BinaryStream; class CraftingDataPacket extends DataPacket { const NETWORK_ID = Info::CRAFTING_DATA_PACKET; const ENTRY_SHAPELESS = 0; const ENTRY_SHAPED = 1; const ENTRY_FURNACE = 2; const ENTRY_FURNACE_DATA = 3; const ENTRY_MULTI = 4; /** @var object[] */ public $entries = []; public $cleanRecipes = false; /** * @return $this */ public function clean(){ $this->entries = []; return parent::clean(); } /** * */ public function decode(){ $entries = []; $recipeCount = $this->getUnsignedVarInt(); for($i = 0; $i < $recipeCount; ++$i){ $entry = []; $entry["type"] = $recipeType = $this->getVarInt(); switch($recipeType){ case self::ENTRY_SHAPELESS: $ingredientCount = $this->getUnsignedVarInt(); /** @var Item */ $entry["input"] = []; for($j = 0; $j < $ingredientCount; ++$j){ $entry["input"][] = $this->getSlot(); } $resultCount = $this->getUnsignedVarInt(); $entry["output"] = []; for($k = 0; $k < $resultCount; ++$k){ $entry["output"][] = $this->getSlot(); } $entry["uuid"] = $this->getUUID()->toString(); break; case self::ENTRY_SHAPED: $entry["width"] = $this->getVarInt(); $entry["height"] = $this->getVarInt(); $count = $entry["width"] * $entry["height"]; $entry["input"] = []; for($j = 0; $j < $count; ++$j){ $entry["input"][] = $this->getSlot(); } $resultCount = $this->getUnsignedVarInt(); $entry["output"] = []; for($k = 0; $k < $resultCount; ++$k){ $entry["output"][] = $this->getSlot(); } $entry["uuid"] = $this->getUUID()->toString(); break; case self::ENTRY_FURNACE: case self::ENTRY_FURNACE_DATA: $entry["inputId"] = $this->getVarInt(); if($recipeType === self::ENTRY_FURNACE_DATA){ $entry["inputDamage"] = $this->getVarInt(); } $entry["output"] = $this->getSlot(); break; case self::ENTRY_MULTI: $entry["uuid"] = $this->getUUID()->toString(); break; default: throw new \UnexpectedValueException("Unhandled recipe type $recipeType!"); //do not continue attempting to decode } $entries[] = $entry; } $this->getBool(); //cleanRecipes } /** * @param $entry * @param BinaryStream $stream * * @return int */ private static function writeEntry($entry, BinaryStream $stream){ if($entry instanceof ShapelessRecipe){ return self::writeShapelessRecipe($entry, $stream); }elseif($entry instanceof ShapedRecipe){ return self::writeShapedRecipe($entry, $stream); }elseif($entry instanceof FurnaceRecipe){ return self::writeFurnaceRecipe($entry, $stream); } //TODO: add MultiRecipe return -1; } /** * @param ShapelessRecipe $recipe * @param BinaryStream $stream * * @return int */ private static function writeShapelessRecipe(ShapelessRecipe $recipe, BinaryStream $stream){ $stream->putUnsignedVarInt($recipe->getIngredientCount()); foreach($recipe->getIngredientList() as $item){ $stream->putSlot($item); } $stream->putUnsignedVarInt(1); $stream->putSlot($recipe->getResult()); $stream->putUUID($recipe->getId()); return CraftingDataPacket::ENTRY_SHAPELESS; } /** * @param ShapedRecipe $recipe * @param BinaryStream $stream * * @return int */ private static function writeShapedRecipe(ShapedRecipe $recipe, BinaryStream $stream){ $stream->putVarInt($recipe->getWidth()); $stream->putVarInt($recipe->getHeight()); for($z = 0; $z < $recipe->getHeight(); ++$z){ for($x = 0; $x < $recipe->getWidth(); ++$x){ $stream->putSlot($recipe->getIngredient($x, $z)); } } $stream->putUnsignedVarInt(1); $stream->putSlot($recipe->getResult()); $stream->putUUID($recipe->getId()); return CraftingDataPacket::ENTRY_SHAPED; } /** * @param FurnaceRecipe $recipe * @param BinaryStream $stream * * @return int */ private static function writeFurnaceRecipe(FurnaceRecipe $recipe, BinaryStream $stream){ if(!$recipe->getInput()->hasAnyDamageValue()){ //Data recipe $stream->putVarInt($recipe->getInput()->getId()); $stream->putVarInt($recipe->getInput()->getDamage()); $stream->putSlot($recipe->getResult()); return CraftingDataPacket::ENTRY_FURNACE_DATA; }else{ $stream->putVarInt($recipe->getInput()->getId()); $stream->putSlot($recipe->getResult()); return CraftingDataPacket::ENTRY_FURNACE; } } /** * @param ShapelessRecipe $recipe */ public function addShapelessRecipe(ShapelessRecipe $recipe){ $this->entries[] = $recipe; } /** * @param ShapedRecipe $recipe */ public function addShapedRecipe(ShapedRecipe $recipe){ $this->entries[] = $recipe; } /** * @param FurnaceRecipe $recipe */ public function addFurnaceRecipe(FurnaceRecipe $recipe){ $this->entries[] = $recipe; } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt(count($this->entries)); $writer = new BinaryStream(); foreach($this->entries as $d){ $entryType = self::writeEntry($d, $writer); if($entryType >= 0){ $this->putVarInt($entryType); $this->put($writer->getBuffer()); }else{ $this->putVarInt(-1); } $writer->reset(); } $this->putBool($this->cleanRecipes); } /** * @return PacketName|string */ public function getName(){ return "CraftingDataPacket"; } } class CraftingEventPacket extends DataPacket { const NETWORK_ID = Info::CRAFTING_EVENT_PACKET; public $windowId; public $type; public $id; public $input = []; public $output = []; /** * @return $this */ public function clean(){ $this->input = []; $this->output = []; return parent::clean(); } /** * */ public function decode(){ $this->windowId = $this->getByte(); $this->type = $this->getVarInt(); $this->id = $this->getUUID(); $size = $this->getUnsignedVarInt(); for($i = 0; $i < $size and $i < 128; ++$i){ $this->input[] = $this->getSlot(); } $size = $this->getUnsignedVarInt(); for($i = 0; $i < $size and $i < 128; ++$i){ $this->output[] = $this->getSlot(); } } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "CraftingEventPacket"; } } use pocketmine\entity\Entity; use pocketmine\item\Item; use pocketmine\utils\BinaryStream; use pocketmine\utils\Utils; abstract class DataPacket extends BinaryStream { const NETWORK_ID = 0; public $isEncoded = false; /** * @return int */ public function pid(){ return $this::NETWORK_ID; } /** * @return mixed */ abstract public function encode(); /** * @return mixed */ abstract public function decode(); public function reset(){ $this->buffer = chr($this::NETWORK_ID); $this->offset = 0; } /** * @return $this */ public function clean(){ $this->buffer = null; $this->isEncoded = false; $this->offset = 0; return $this; } /** * @return array */ public function __debugInfo(){ $data = []; foreach($this as $k => $v){ if($k === "buffer"){ $data[$k] = bin2hex($v); }elseif(is_string($v) or (is_object($v) and method_exists($v, "__toString"))){ $data[$k] = Utils::printable((string) $v); }else{ $data[$k] = $v; } } return $data; } /** * @param bool $types * * @return array */ public function getEntityMetadata(bool $types = true) : array{ $count = $this->getUnsignedVarInt(); $data = []; for($i = 0; $i < $count; ++$i){ $key = $this->getUnsignedVarInt(); $type = $this->getUnsignedVarInt(); $value = null; switch($type){ case Entity::DATA_TYPE_BYTE: $value = $this->getByte(); break; case Entity::DATA_TYPE_SHORT: $value = $this->getLShort(true); //signed break; case Entity::DATA_TYPE_INT: $value = $this->getVarInt(); break; case Entity::DATA_TYPE_FLOAT: $value = $this->getLFloat(); break; case Entity::DATA_TYPE_STRING: $value = $this->getString(); break; case Entity::DATA_TYPE_SLOT: //TODO: use objects directly $value = []; $item = $this->getSlot(); $value[0] = $item->getId(); $value[1] = $item->getCount(); $value[2] = $item->getDamage(); break; case Entity::DATA_TYPE_POS: $value = []; $value[0] = $this->getVarInt(); //x $value[1] = $this->getVarInt(); //y (SIGNED) $value[2] = $this->getVarInt(); //z break; case Entity::DATA_TYPE_LONG: $value = $this->getVarInt(); //TODO: varint64 proper support break; case Entity::DATA_TYPE_VECTOR3F: $value = [0.0, 0.0, 0.0]; $this->getVector3f($value[0], $value[1], $value[2]); break; default: $value = []; } if($types === true){ $data[$key] = [$value, $type]; }else{ $data[$key] = $value; } } return $data; } /** * @param array $metadata */ public function putEntityMetadata(array $metadata){ $this->putUnsignedVarInt(count($metadata)); foreach($metadata as $key => $d){ $this->putUnsignedVarInt($key); //data key $this->putUnsignedVarInt($d[0]); //data type switch($d[0]){ case Entity::DATA_TYPE_BYTE: $this->putByte($d[1]); break; case Entity::DATA_TYPE_SHORT: $this->putLShort($d[1]); //SIGNED short! break; case Entity::DATA_TYPE_INT: $this->putVarInt($d[1]); break; case Entity::DATA_TYPE_FLOAT: $this->putLFloat($d[1]); break; case Entity::DATA_TYPE_STRING: $this->putString($d[1]); break; case Entity::DATA_TYPE_SLOT: //TODO: change this implementation (use objects) $this->putSlot(Item::get($d[1][0], $d[1][2], $d[1][1])); //ID, damage, count break; case Entity::DATA_TYPE_POS: //TODO: change this implementation (use objects) $this->putVarInt($d[1][0]); //x $this->putVarInt($d[1][1]); //y (SIGNED) $this->putVarInt($d[1][2]); //z break; case Entity::DATA_TYPE_LONG: $this->putVarInt($d[1]); //TODO: varint64 support break; case Entity::DATA_TYPE_VECTOR3F: //TODO: change this implementation (use objects) $this->putVector3f($d[1][0], $d[1][1], $d[1][2]); //x, y, z } } } /** * @return PacketName|string */ public function getName(){ return "DataPacket"; } } class DisconnectPacket extends DataPacket { const NETWORK_ID = Info::DISCONNECT_PACKET; public $hideDisconnectionScreen = false; public $message; /** * */ public function decode(){ $this->hideDisconnectionScreen = $this->getBool(); $this->message = $this->getString(); } /** * */ public function encode(){ $this->reset(); $this->putBool($this->hideDisconnectionScreen); if(!$this->hideDisconnectionScreen){ $this->putString($this->message); } } } class DropItemPacket extends DataPacket { const NETWORK_ID = Info::DROP_ITEM_PACKET; public $type; public $item; /** * */ public function decode(){ $this->type = $this->getByte(); $this->item = $this->getSlot(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "DropItemPacket"; } } class EntityEventPacket extends DataPacket { const NETWORK_ID = Info::ENTITY_EVENT_PACKET; const HURT_ANIMATION = 2; const DEATH_ANIMATION = 3; const TAME_FAIL = 6; const TAME_SUCCESS = 7; const SHAKE_WET = 8; const USE_ITEM = 9; const EAT_GRASS_ANIMATION = 10; const FISH_HOOK_BUBBLE = 11; const FISH_HOOK_POSITION = 12; const FISH_HOOK_HOOK = 13; const FISH_HOOK_TEASE = 14; const SQUID_INK_CLOUD = 15; const AMBIENT_SOUND = 16; const RESPAWN = 18; //TODO add new events public $eid; public $event; public $unknown; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->event = $this->getByte(); $this->unknown = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putByte($this->event); $this->putVarInt($this->unknown); } /** * @return PacketName|string */ public function getName(){ return "EntityEventPacket"; } } class EntityFallPacket extends DataPacket { const NETWORK_ID = Info::ENTITY_FALL_PACKET; public $entityRuntimeId; public $fallDistance; public $bool1; /** * */ public function decode(){ $this->entityRuntimeId = $this->getEntityId(); $this->fallDistance = $this->getLFloat(); $this->bool1 = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityRuntimeId); $this->putLFloat($this->fallDistance); $this->putBool($this->bool1); } } class ExplodePacket extends DataPacket { const NETWORK_ID = Info::EXPLODE_PACKET; public $x; public $y; public $z; public $radius; public $records = []; /** * @return $this */ public function clean(){ $this->records = []; return parent::clean(); } /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVector3f($this->x, $this->y, $this->z); $this->putLFloat($this->radius); $this->putUnsignedVarInt(count($this->records)); if(count($this->records) > 0){ foreach($this->records as $record){ $this->putBlockCoords($record->x, $record->y, $record->z); } } } /** * @return PacketName|string */ public function getName(){ return "ExplodePacket"; } } class FullChunkDataPacket extends DataPacket { const NETWORK_ID = Info::FULL_CHUNK_DATA_PACKET; public $chunkX; public $chunkZ; public $data; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->chunkX); $this->putVarInt($this->chunkZ); $this->putString($this->data); } /** * @return PacketName|string */ public function getName(){ return "FullChunkDataPacket"; } } class HurtArmorPacket extends DataPacket { const NETWORK_ID = Info::HURT_ARMOR_PACKET; public $health; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->health); } /** * @return PacketName|string */ public function getName(){ return "HurtArmorPacket"; } } class InteractPacket extends DataPacket { const NETWORK_ID = Info::INTERACT_PACKET; const ACTION_RIGHT_CLICK = 1; const ACTION_LEFT_CLICK = 2; const ACTION_LEAVE_VEHICLE = 3; const ACTION_MOUSEOVER = 4; public $action; public $eid; public $target; /** * */ public function decode(){ $this->action = $this->getByte(); $this->target = $this->getEntityId(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->action); $this->putEntityId($this->target); } /** * @return PacketName|string */ public function getName(){ return "InteractPacket"; } } class InventoryActionPacket extends DataPacket { const NETWORK_ID = Info::INVENTORY_ACTION_PACKET; const ACTION_GIVE_ITEM = 0; const ACTION_ENCHANT_ITEM = 2; public $actionId; public $item; public $enchantmentId = 0; public $enchantmentLevel = 0; /** * */ public function decode(){ $this->actionId = $this->getUnsignedVarInt(); $this->item = $this->getSlot(); $this->enchantmentId = $this->getVarInt(); $this->enchantmentLevel = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt($this->actionId); $this->putSlot($this->item); $this->putVarInt($this->enchantmentId); $this->putVarInt($this->enchantmentLevel); } } class ItemFrameDropItemPacket extends DataPacket { const NETWORK_ID = Info::ITEM_FRAME_DROP_ITEM_PACKET; public $x; public $y; public $z; //public $item; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); //$this->item = $this->getSlot(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "ItemFrameDropItemPacket"; } } class LevelEventPacket extends DataPacket { const NETWORK_ID = Info::LEVEL_EVENT_PACKET; const EVENT_SOUND_CLICK = 1000; const EVENT_SOUND_CLICK_FAIL = 1001; const EVENT_SOUND_SHOOT = 1002; const EVENT_SOUND_DOOR = 1003; const EVENT_SOUND_FIZZ = 1004; const EVENT_SOUND_IGNITE = 1005; const EVENT_SOUND_GHAST = 1007; const EVENT_SOUND_GHAST_SHOOT = 1008; const EVENT_SOUND_BLAZE_SHOOT = 1009; const EVENT_SOUND_DOOR_BUMP = 1010; const EVENT_SOUND_DOOR_CRASH = 1012; const EVENT_SOUND_ENDERMAN_TELEPORT = 1018; const EVENT_SOUND_ANVIL_BREAK = 1020; //This sound is played on the anvil's final use, NOT when the block is broken. const EVENT_SOUND_ANVIL_USE = 1021; const EVENT_SOUND_ANVIL_FALL = 1022; const EVENT_SOUND_POP = 1030; const EVENT_SOUND_PORTAL = 1032; const EVENT_SOUND_ITEMFRAME_ADD_ITEM = 1040; const EVENT_SOUND_ITEMFRAME_REMOVE = 1041; const EVENT_SOUND_ITEMFRAME_PLACE = 1042; const EVENT_SOUND_ITEMFRAME_REMOVE_ITEM = 1043; const EVENT_SOUND_ITEMFRAME_ROTATE_ITEM = 1044; const EVENT_SOUND_CAMERA = 1050; const EVENT_SOUND_ORB = 1051; const EVENT_PARTICLE_SHOOT = 2000; const EVENT_PARTICLE_DESTROY = 2001; const EVENT_PARTICLE_SPLASH = 2002; //This is actually the splash potion sound with particles const EVENT_PARTICLE_EYE_DESPAWN = 2003; const EVENT_PARTICLE_SPAWN = 2004; const EVENT_GUARDIAN_CURSE = 2006; const EVENT_PARTICLE_BLOCK_FORCE_FIELD = 2008; const EVENT_PARTICLE_PUNCH_BLOCK = 2014; const EVENT_START_RAIN = 3001; const EVENT_START_THUNDER = 3002; const EVENT_STOP_RAIN = 3003; const EVENT_STOP_THUNDER = 3004; const EVENT_REDSTONE_TRIGGER = 3500; const EVENT_CAULDRON_EXPLODE = 3501; const EVENT_CAULDRON_DYE_ARMOR = 3502; const EVENT_CAULDRON_CLEAN_ARMOR = 3503; const EVENT_CAULDRON_FILL_POTION = 3504; const EVENT_CAULDRON_TAKE_POTION = 3505; const EVENT_CAULDRON_FILL_WATER = 3506; const EVENT_CAULDRON_TAKE_WATER = 3507; const EVENT_CAULDRON_ADD_DYE = 3508; const EVENT_BLOCK_START_BREAK = 3600; const EVENT_BLOCK_STOP_BREAK = 3601; const EVENT_SET_DATA = 4000; const EVENT_PLAYERS_SLEEPING = 9800; const EVENT_ADD_PARTICLE_MASK = 0x4000; public $evid; public $x = 0; //Weather effects don't have coordinates public $y = 0; public $z = 0; public $data; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->evid); $this->putVector3f($this->x, $this->y, $this->z); $this->putVarInt($this->data); } } class LevelSoundEventPacket extends DataPacket { const NETWORK_ID = Info::LEVEL_SOUND_EVENT_PACKET; const SOUND_ITEM_USE_ON = 0; const SOUND_HIT = 1; const SOUND_STEP = 2; const SOUND_JUMP = 3; const SOUND_BREAK = 4; const SOUND_PLACE = 5; const SOUND_HEAVY_STEP = 6; const SOUND_GALLOP = 7; const SOUND_FALL = 8; const SOUND_AMBIENT = 9; const SOUND_AMBIENT_BABY = 10; const SOUND_AMBIENT_IN_WATER = 11; const SOUND_BREATHE = 12; const SOUND_DEATH = 13; const SOUND_DEATH_IN_WATER = 14; const SOUND_DEATH_TO_ZOMBIE = 15; const SOUND_HURT = 16; const SOUND_HURT_IN_WATER = 17; const SOUND_MAD = 18; const SOUND_BOOST = 19; const SOUND_BOW = 20; const SOUND_SQUISH_BIG = 21; const SOUND_SQUISH_SMALL = 22; const SOUND_FALL_BIG = 23; const SOUND_FALL_SMALL = 24; const SOUND_SPLASH = 25; const SOUND_FIZZ = 26; const SOUND_FLAP = 27; const SOUND_SWIM = 28; const SOUND_DRINK = 29; const SOUND_EAT = 30; const SOUND_TAKEOFF = 31; const SOUND_SHAKE = 32; const SOUND_PLOP = 33; const SOUND_LAND = 34; const SOUND_SADDLE = 35; const SOUND_ARMOR = 36; const SOUND_ADD_CHEST = 37; const SOUND_THROW = 38; const SOUND_ATTACK = 39; const SOUND_ATTACK_NODAMAGE = 40; const SOUND_WARN = 41; const SOUND_SHEAR = 42; const SOUND_MILK = 43; const SOUND_THUNDER = 44; const SOUND_EXPLODE = 45; const SOUND_FIRE = 46; const SOUND_IGNITE = 47; const SOUND_FUSE = 48; const SOUND_STARE = 49; const SOUND_SPAWN = 50; const SOUND_SHOOT = 51; const SOUND_BREAK_BLOCK = 52; const SOUND_REMEDY = 53; const SOUND_UNFECT = 54; const SOUND_LEVELUP = 55; const SOUND_BOW_HIT = 56; const SOUND_BULLET_HIT = 57; const SOUND_EXTINGUISH_FIRE = 58; const SOUND_ITEM_FIZZ = 59; const SOUND_CHEST_OPEN = 60; const SOUND_CHEST_CLOSED = 61; const SOUND_SHULKERBOX_OPEN = 62; const SOUND_SHULKERBOX_CLOSED = 63; const SOUND_POWER_ON = 64; const SOUND_POWER_OFF = 65; const SOUND_ATTACH = 66; const SOUND_DETACH = 67; const SOUND_DENY = 68; const SOUND_TRIPOD = 69; const SOUND_POP = 70; const SOUND_DROP_SLOT = 71; const SOUND_NOTE = 72; const SOUND_THORNS = 73; const SOUND_PISTON_IN = 74; const SOUND_PISTON_OUT = 75; const SOUND_PORTAL = 76; const SOUND_WATER = 77; const SOUND_LAVA_POP = 78; const SOUND_LAVA = 79; const SOUND_BURP = 80; const SOUND_BUCKET_FILL_WATER = 81; const SOUND_BUCKET_FILL_LAVA = 82; const SOUND_BUCKET_EMPTY_WATER = 83; const SOUND_BUCKET_EMPTY_LAVA = 84; const SOUND_GUARDIAN_FLOP = 85; const SOUND_ELDERGUARDIAN_CURSE = 86; const SOUND_MOB_WARNING = 87; const SOUND_MOB_WARNING_BABY = 88; const SOUND_TELEPORT = 89; const SOUND_SHULKER_OPEN = 90; const SOUND_SHULKER_CLOSE = 91; const SOUND_HAGGLE = 92; const SOUND_HAGGLE_YES = 93; const SOUND_HAGGLE_NO = 94; const SOUND_HAGGLE_IDLE = 95; const SOUND_CHORUSGROW = 96; const SOUND_CHORUSDEATH = 97; const SOUND_GLASS = 98; const SOUND_CAST_SPELL = 99; const SOUND_PREPARE_ATTACK = 100; const SOUND_PREPARE_SUMMON = 101; const SOUND_PREPARE_WOLOLO = 102; const SOUND_FANG = 103; const SOUND_CHARGE = 104; const SOUND_CAMERA_TAKE_PICTURE = 105; const SOUND_DEFAULT = 106; const SOUND_UNDEFINED = 107; public $sound; public $x; public $y; public $z; public $extraData = -1; public $pitch = 1; public $unknownBool = false; public $unknownBool2 = false; /** * */ public function decode(){ $this->sound = $this->getByte(); $this->getVector3f($this->x, $this->y, $this->z); $this->extraData = $this->getVarInt(); $this->pitch = $this->getVarInt(); $this->unknownBool = $this->getBool(); $this->unknownBool2 = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->sound); $this->putVector3f($this->x, $this->y, $this->z); $this->putVarInt($this->extraData); $this->putVarInt($this->pitch); $this->putBool($this->unknownBool); $this->putBool($this->unknownBool2); } /** * @return PacketName|string */ public function getName(){ return "LevelSoundEventPacket"; } } use pocketmine\Server; class LoginPacket extends DataPacket { const NETWORK_ID = Info::LOGIN_PACKET; const MOJANG_PUBKEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V"; const EDITION_POCKET = 0; public $username; public $protocol; public $gameEdition; public $clientUUID; public $clientId; public $identityPublicKey; public $serverAddress; public $skinId = null; public $skin = null; public $clientData = []; public $deviceModel; public $deviceOS; /** * */ public function decode(){ $this->protocol = $this->getInt(); if(!Server::getInstance()->allVersion and !in_array($this->protocol, Info::ACCEPTED_PROTOCOLS)){ $this->buffer = null; return; } $this->gameEdition = $this->getByte(); $this->setBuffer($this->getString(), 0); $time = time(); $chainData = json_decode($this->get($this->getLInt()))->{"chain"}; // Start with the trusted one $chainKey = self::MOJANG_PUBKEY; while(!empty($chainData)){ foreach($chainData as $index => $chain){ list($verified, $webtoken) = $this->decodeToken($chain, $chainKey); if(isset($webtoken["extraData"])){ if(isset($webtoken["extraData"]["displayName"])){ $this->username = $webtoken["extraData"]["displayName"]; } if(isset($webtoken["extraData"]["identity"])){ $this->clientUUID = $webtoken["extraData"]["identity"]; } } if($verified){ $verified = isset($webtoken["nbf"]) && $webtoken["nbf"] <= $time && isset($webtoken["exp"]) && $webtoken["exp"] > $time; } if($verified and isset($webtoken["identityPublicKey"])){ // Looped key chain. #blamemojang if($webtoken["identityPublicKey"] != self::MOJANG_PUBKEY) $chainKey = $webtoken["identityPublicKey"]; break; }elseif($chainKey === null){ // We have already gave up break; } } if(!$verified && $chainKey !== null){ $chainKey = null; }else{ unset($chainData[$index]); } } list($verified, $this->clientData) = $this->decodeToken($this->get($this->getLInt()), $chainKey); $this->clientId = $this->clientData["ClientRandomId"] ?? null; $this->serverAddress = $this->clientData["ServerAddress"] ?? null; $this->skinId = $this->clientData["SkinId"] ?? null; if(isset($this->clientData["SkinData"])){ $this->skin = base64_decode($this->clientData["SkinData"]); } if(isset($this->clientData["DeviceModel"])){ $this->deviceModel = $this->clientData["DeviceModel"]; } if(isset($this->clientData["DeviceOS"])){ $this->deviceOS = $this->clientData["DeviceOS"]; } if($verified){ $this->identityPublicKey = $chainKey; } } /** * */ public function encode(){ } /** * @param $token * @param $key * * @return array */ public function decodeToken($token, $key){ $tokens = explode(".", $token); list($headB64, $payloadB64, $sigB64) = $tokens; if($key !== null and extension_loaded("openssl")){ $sig = base64_decode(strtr($sigB64, '-_', '+/'), true); $rawLen = 48; // ES384 for($i = $rawLen; $i > 0 and $sig[$rawLen - $i] == chr(0); $i--){ } $j = $i + (ord($sig[$rawLen - $i]) >= 128 ? 1 : 0); for($k = $rawLen; $k > 0 and $sig[2 * $rawLen - $k] == chr(0); $k--){ } $l = $k + (ord($sig[2 * $rawLen - $k]) >= 128 ? 1 : 0); $len = 2 + $j + 2 + $l; $derSig = chr(48); if($len > 255){ throw new \RuntimeException("Invalid signature format"); }elseif($len >= 128){ $derSig .= chr(81); } $derSig .= chr($len) . chr(2) . chr($j); $derSig .= str_repeat(chr(0), $j - $i) . substr($sig, $rawLen - $i, $i); $derSig .= chr(2) . chr($l); $derSig .= str_repeat(chr(0), $l - $k) . substr($sig, 2 * $rawLen - $k, $k); $verified = openssl_verify($headB64 . "." . $payloadB64, $derSig, "-----BEGIN PUBLIC KEY-----\n" . wordwrap($key, 64, "\n", true) . "\n-----END PUBLIC KEY-----\n", OPENSSL_ALGO_SHA384) === 1; }else{ $verified = false; } return [$verified, json_decode(base64_decode($payloadB64), true)]; } } uuid = $this->getEntityId(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->uuid); } } class MobArmorEquipmentPacket extends DataPacket { const NETWORK_ID = Info::MOB_ARMOR_EQUIPMENT_PACKET; public $eid; public $slots = []; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->slots[0] = $this->getSlot(); $this->slots[1] = $this->getSlot(); $this->slots[2] = $this->getSlot(); $this->slots[3] = $this->getSlot(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putSlot($this->slots[0]); $this->putSlot($this->slots[1]); $this->putSlot($this->slots[2]); $this->putSlot($this->slots[3]); } } class MobEffectPacket extends DataPacket { const NETWORK_ID = Info::MOB_EFFECT_PACKET; const EVENT_ADD = 1; const EVENT_MODIFY = 2; const EVENT_REMOVE = 3; public $eid; public $eventId; public $effectId; public $amplifier; public $particles = true; public $duration; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putByte($this->eventId); $this->putVarInt($this->effectId); $this->putVarInt($this->amplifier); $this->putBool($this->particles); $this->putVarInt($this->duration); } /** * @return PacketName|string */ public function getName(){ return "MobEffectPacket"; } } class MobEquipmentPacket extends DataPacket { const NETWORK_ID = Info::MOB_EQUIPMENT_PACKET; public $eid; public $item; public $slot; public $selectedSlot; public $windowId; /** * */ public function decode(){ $this->eid = $this->getEntityId(); //EntityRuntimeID $this->item = $this->getSlot(); $this->slot = $this->getByte(); $this->selectedSlot = $this->getByte(); $this->windowId = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityRuntimeID $this->putSlot($this->item); $this->putByte($this->slot); $this->putByte($this->selectedSlot); $this->putByte($this->windowId); } } class MoveEntityPacket extends DataPacket { const NETWORK_ID = Info::MOVE_ENTITY_PACKET; public $eid; public $x; public $y; public $z; public $yaw; public $headYaw; public $pitch; public $byte1; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->getVector3f($this->x, $this->y, $this->z); $this->pitch = $this->getByte() * (360.0 / 256); $this->yaw = $this->getByte() * (360.0 / 256); $this->headYaw = $this->getByte() * (360.0 / 256); $this->byte1 = $this->getByte(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVector3f($this->x, $this->y, $this->z); $this->putByte($this->pitch / (360.0 / 256)); $this->putByte($this->yaw / (360.0 / 256)); $this->putByte($this->headYaw / (360.0 / 256)); $this->putByte($this->byte1); } } class MovePlayerPacket extends DataPacket { const NETWORK_ID = Info::MOVE_PLAYER_PACKET; const MODE_NORMAL = 0; const MODE_RESET = 1; const MODE_ROTATION = 2; public $eid; public $x; public $y; public $z; public $yaw; public $bodyYaw; public $pitch; public $mode = self::MODE_NORMAL; public $onGround; public $eid2; /** * @return $this */ public function clean(){ $this->teleport = false; return parent::clean(); } /** * */ public function decode(){ $this->eid = $this->getEntityId(); //EntityRuntimeID $this->getVector3f($this->x, $this->y, $this->z); $this->pitch = $this->getLFloat(); $this->yaw = $this->getLFloat(); $this->bodyYaw = $this->getLFloat(); $this->mode = $this->getByte(); $this->onGround = $this->getBool(); $this->eid2 = $this->getEntityId(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); //EntityRuntimeID $this->putVector3f($this->x, $this->y, $this->z); $this->putLFloat($this->pitch); $this->putLFloat($this->yaw); $this->putLFloat($this->bodyYaw); //TODO $this->putByte($this->mode); $this->putBool($this->onGround); $this->putEntityId($this->eid2); //EntityRuntimeID } } class PlaySoundPacket extends DataPacket { const NETWORK_ID = Info::PLAY_SOUND_PACKET; public $sound; public $x; public $y; public $z; public $volume; public $float; /** * */ public function decode(){ $this->sound = $this->getString(); $this->getBlockPos($this->x, $this->y, $this->z); $this->volume = $this->getFloat(); $this->float = $this->getFloat(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->sound); $this->putBlockPos($this->x, $this->y, $this->z); $this->putFloat($this->volume); $this->putFloat($this->float); } /** * @return PacketName|string */ public function getName(){ return "PlaySoundPacket"; } } class PlayStatusPacket extends DataPacket { const NETWORK_ID = Info::PLAY_STATUS_PACKET; const LOGIN_SUCCESS = 0; const LOGIN_FAILED_CLIENT = 1; const LOGIN_FAILED_SERVER = 2; const PLAYER_SPAWN = 3; const LOGIN_FAILED_INVALID_TENANT = 4; const LOGIN_FAILED_VANILLA_EDU = 5; const LOGIN_FAILED_EDU_VANILLA = 6; public $status; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putInt($this->status); } } class PlayerActionPacket extends DataPacket { const NETWORK_ID = Info::PLAYER_ACTION_PACKET; const ACTION_START_BREAK = 0; const ACTION_ABORT_BREAK = 1; const ACTION_STOP_BREAK = 2; const ACTION_RELEASE_ITEM = 5; const ACTION_STOP_SLEEPING = 6; const ACTION_SPAWN_SAME_DIMENSION = 7; const ACTION_JUMP = 8; const ACTION_START_SPRINT = 9; const ACTION_STOP_SPRINT = 10; const ACTION_START_SNEAK = 11; const ACTION_STOP_SNEAK = 12; const ACTION_SPAWN_OVERWORLD = 13; const ACTION_SPAWN_NETHER = 14; const ACTION_START_GLIDE = 15; const ACTION_STOP_GLIDE = 16; const ACTION_BUILD_DENIED = 17; const ACTION_CONTINUE_BREAK = 18; public $eid; public $action; public $x; public $y; public $z; public $face; /** * */ public function decode(){ $this->eid = $this->getEntityId(); $this->action = $this->getVarInt(); $this->getBlockCoords($this->x, $this->y, $this->z); $this->face = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVarInt($this->action); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putVarInt($this->face); } } class PlayerInputPacket extends DataPacket { const NETWORK_ID = Info::PLAYER_INPUT_PACKET; public $motionX; public $motionY; public $unknownBool1; public $unknownBool2; /** * */ public function decode(){ $this->motionX = $this->getLFloat(); $this->motionY = $this->getLFloat(); $this->unknownBool1 = $this->getBool(); $this->unknownBool2 = $this->getBool(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "PlayerInputPacket"; } } class PlayerListPacket extends DataPacket { const NETWORK_ID = Info::PLAYER_LIST_PACKET; const TYPE_ADD = 0; const TYPE_REMOVE = 1; //REMOVE: UUID, ADD: UUID, entity id, name, skinId, skin /** @var array[] */ public $entries = []; public $type; /** * @return $this */ public function clean(){ $this->entries = []; return parent::clean(); } /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putByte($this->type); $this->putUnsignedVarInt(count($this->entries)); foreach($this->entries as $d){ if($this->type === self::TYPE_ADD){ $this->putUUID($d[0]); $this->putEntityId($d[1]); $this->putString($d[2]); $this->putString($d[3]); $this->putString($d[4]); }else{ $this->putUUID($d[0]); } } } /** * @return PacketName|string */ public function getName(){ return "PlayerListPacket"; } } class RemoveBlockPacket extends DataPacket { const NETWORK_ID = Info::REMOVE_BLOCK_PACKET; public $x; public $y; public $z; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "RemoveBlockPacket"; } } class RemoveEntityPacket extends DataPacket { const NETWORK_ID = Info::REMOVE_ENTITY_PACKET; public $eid; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); } } class ReplaceItemInSlotPacket extends DataPacket { const NETWORK_ID = Info::REPLACE_ITEM_IN_SLOT_PACKET; public $item; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putSlot($this->item); } }radius = $this->getVarInt(); } /** * */ public function encode(){ } } class ResourcePackChunkDataPacket extends DataPacket { const NETWORK_ID = Info::RESOURCE_PACK_CHUNK_DATA_PACKET; public $packId; public $chunkIndex; public $progress; public $data; /** * */ public function decode(){ $this->packId = $this->getString(); $this->chunkIndex = $this->getLInt(); $this->progress = $this->getLLong(); $this->data = $this->get($this->getLInt()); } /** * */ public function encode(){ $this->reset(); $this->putString($this->packId); $this->putLInt($this->chunkIndex); $this->putLLong($this->progress); $this->putLInt(strlen($this->data)); $this->put($this->data); } } class ResourcePackChunkRequestPacket extends DataPacket { const NETWORK_ID = Info::RESOURCE_PACK_CHUNK_REQUEST_PACKET; public $packId; public $chunkIndex; /** * */ public function decode(){ $this->packId = $this->getString(); $this->chunkIndex = $this->getLInt(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->packId); $this->putLInt($this->chunkIndex); } } class ResourcePackClientResponsePacket extends DataPacket { const NETWORK_ID = Info::RESOURCE_PACK_CLIENT_RESPONSE_PACKET; const STATUS_REFUSED = 1; const STATUS_SEND_PACKS = 2; const STATUS_HAVE_ALL_PACKS = 3; const STATUS_COMPLETED = 4; public $status; public $packIds = []; /** * */ public function decode(){ $this->status = $this->getByte(); $entryCount = $this->getLShort(); while($entryCount-- > 0){ $this->packIds[] = $this->getString(); } } /** * */ public function encode(){ $this->reset(); $this->putByte($this->status); $this->putLShort(count($this->packIds)); foreach($this->packIds as $id){ $this->putString($id); } } } class ResourcePackDataInfoPacket extends DataPacket { const NETWORK_ID = Info::RESOURCE_PACK_DATA_INFO_PACKET; public $packId; public $maxChunkSize; public $chunkCount; public $compressedPackSize; public $sha256; /** * */ public function decode(){ $this->packId = $this->getString(); $this->maxChunkSize = $this->getLInt(); $this->chunkCount = $this->getLInt(); $this->compressedPackSize = $this->getLLong(); $this->sha256 = $this->getString(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->packId); $this->putLInt($this->maxChunkSize); $this->putLInt($this->chunkCount); $this->putLLong($this->compressedPackSize); $this->putString($this->sha256); } } use pocketmine\resourcepacks\ResourcePack; class ResourcePackStackPacket extends DataPacket { const NETWORK_ID = Info::RESOURCE_PACK_STACK_PACKET; public $mustAccept = false; /** @var ResourcePack[] */ public $behaviorPackStack = []; /** @var ResourcePack[] */ public $resourcePackStack = []; /** * */ public function decode(){ /*$this->mustAccept = $this->getBool(); $behaviorPackCount = $this->getLShort(); while($behaviorPackCount-- > 0){ $packId = $this->getString(); $version = $this->getString(); $this->behaviorPackStack[] = new ResourcePackInfoEntry($packId, $version); } $resourcePackCount = $this->getLShort(); while($resourcePackCount-- > 0){ $packId = $this->getString(); $version = $this->getString(); $this->resourcePackStack[] = new ResourcePackInfoEntry($packId, $version); }*/ } /** * */ public function encode(){ $this->reset(); $this->putBool($this->mustAccept); $this->putUnsignedVarInt(count($this->behaviorPackStack)); foreach($this->behaviorPackStack as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); } $this->putUnsignedVarInt(count($this->resourcePackStack)); foreach($this->resourcePackStack as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); } } } use pocketmine\resourcepacks\ResourcePackInfoEntry; class ResourcePacksInfoPacket extends DataPacket { const NETWORK_ID = Info::RESOURCE_PACKS_INFO_PACKET; public $mustAccept = false; //force client to use selected resource packs /** @var ResourcePackInfoEntry */ public $behaviorPackEntries = []; /** @var ResourcePackInfoEntry */ public $resourcePackEntries = []; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBool($this->mustAccept); $this->putLShort(count($this->behaviorPackEntries)); foreach($this->behaviorPackEntries as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); $this->putLLong($entry->getPackSize()); } $this->putLShort(count($this->resourcePackEntries)); foreach($this->resourcePackEntries as $entry){ $this->putString($entry->getPackId()); $this->putString($entry->getPackVersion()); $this->putLLong($entry->getPackSize()); } } } class RespawnPacket extends DataPacket { const NETWORK_ID = Info::RESPAWN_PACKET; public $x; public $y; public $z; /** * */ public function decode(){ $this->x = $this->getLFloat(); $this->y = $this->getLFloat(); $this->z = $this->getLFloat(); } /** * */ public function encode(){ $this->reset(); $this->putLFloat($this->x); $this->putLFloat($this->y); $this->putLFloat($this->z); } /** * @return PacketName|string */ public function getName(){ return "RespawnPacket"; } } class RiderJumpPacket extends DataPacket { const NETWORK_ID = Info::RIDER_JUMP_PACKET; public $unknown; /** * */ public function decode(){ $this->unknown = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->unknown); } } class ServerToClientHandshakePacket extends DataPacket { const NETWORK_ID = Info::SERVER_TO_CLIENT_HANDSHAKE_PACKET; public $publicKey; public $serverToken; /** * @return bool */ public function canBeSentBeforeLogin() : bool{ return true; } /** * */ public function decode(){ $this->publicKey = $this->getString(); $this->serverToken = $this->getString(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->publicKey); $this->putString($this->serverToken); } } class SetCommandsEnabledPacket extends DataPacket { const NETWORK_ID = Info::SET_COMMANDS_ENABLED_PACKET; public $enabled; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBool($this->enabled); } /** * @return PacketName|string */ public function getName(){ return "SetCommandsEnabledPacket"; } } class SetDifficultyPacket extends DataPacket { const NETWORK_ID = Info::SET_DIFFICULTY_PACKET; public $difficulty; /** * */ public function decode(){ $this->difficulty = $this->getUnsignedVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putUnsignedVarInt($this->difficulty); } /** * @return PacketName|string */ public function getName(){ return "SetDifficultyPacket"; } }reset(); $this->putEntityId($this->eid); $this->putEntityMetadata($this->metadata); } /** * @return PacketName|string */ public function getName(){ return "SetEntityDataPacket"; } } class SetEntityLinkPacket extends DataPacket { const NETWORK_ID = Info::SET_ENTITY_LINK_PACKET; const TYPE_REMOVE = 0; const TYPE_RIDE = 1; const TYPE_PASSENGER = 2; public $from; public $to; public $type; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->from); $this->putEntityId($this->to); $this->putByte($this->type); } /** * @return PacketName|string */ public function getName(){ return "SetEntityLinkPacket"; } } class SetEntityMotionPacket extends DataPacket { const NETWORK_ID = Info::SET_ENTITY_MOTION_PACKET; public $eid; public $motionX; public $motionY; public $motionZ; /** * @return $this */ public function clean(){ $this->entities = []; return parent::clean(); } /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVector3f($this->motionX, $this->motionY, $this->motionZ); } /** * @return PacketName|string */ public function getName(){ return "SetEntityMotionPacket"; } } class SetHealthPacket extends DataPacket { const NETWORK_ID = Info::SET_HEALTH_PACKET; public $health; /** * */ public function decode(){ $this->health = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->health); } /** * @return PacketName|string */ public function getName(){ return "SetHealthPacket"; } } class SetPlayerGameTypePacket extends DataPacket { const NETWORK_ID = Info::SET_PLAYER_GAME_TYPE_PACKET; public $gamemode; /** * */ public function decode(){ $this->gamemode = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->gamemode); } /** * @return PacketName|string */ public function getName(){ return "SetPlayerGameTypePacket"; } } class SetSpawnPositionPacket extends DataPacket { const NETWORK_ID = Info::SET_SPAWN_POSITION_PACKET; public $unknown; public $x; public $y; public $z; public $unknownBool; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->unknown); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putBool($this->unknownBool); } /** * @return PacketName|string */ public function getName(){ return "SetSpawnPositionPacket"; } } class SetTimePacket extends DataPacket { const NETWORK_ID = Info::SET_TIME_PACKET; public $time; public $started = true; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->time); $this->putBool($this->started); } /** * @return PacketName|string */ public function getName(){ return "SetTimePacket"; } } class SetTitlePacket extends DataPacket { const NETWORK_ID = Info::SET_TITLE_PACKET; const TYPE_CLEAR = 0; const TYPE_RESET = 1; const TYPE_TITLE = 2; const TYPE_SUB_TITLE = 3; const TYPE_ACTION_BAR = 4; const TYPE_TIMES = 5; public $type; public $title; public $fadeInDuration; public $duration; public $fadeOutDuration; /** * */ public function decode(){ $this->type = $this->getVarInt(); $this->title = $this->getString(); $this->fadeInDuration = $this->getVarInt(); $this->duration = $this->getVarInt(); $this->fadeOutDuration = $this->getVarInt(); } /** * */ public function encode(){ $this->reset(); $this->putVarInt($this->type); $this->putString($this->title); $this->putVarInt($this->fadeInDuration); $this->putVarInt($this->duration); $this->putVarInt($this->fadeOutDuration); } /** * @return PacketName|string */ public function getName(){ return "SetTitlePacket"; } } class ShowCreditsPacket extends DataPacket { const NETWORK_ID = Info::SHOW_CREDITS_PACKET; public $eid; public $type; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->eid); $this->putVarInt($this->type); } /** * @return PacketName|string */ public function getName(){ return "ShowCreditsPacket"; } } class SpawnExperienceOrbPacket extends DataPacket { const NETWORK_ID = Info::SPAWN_EXPERIENCE_ORB_PACKET; public $x; public $y; public $z; public $amount; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putVector3f($this->x, $this->y, $this->z); $this->putVarInt($this->amount); } /** * @return PacketName|string */ public function getName(){ return "SpawnExperienceOrbPacket"; } } class StartGamePacket extends DataPacket { const NETWORK_ID = Info::START_GAME_PACKET; public $entityUniqueId; public $entityRuntimeId; public $playerGamemode; public $x; public $y; public $z; public $pitch; public $yaw; public $seed; public $dimension; public $generator = 1; //default infinite - 0 old, 1 infinite, 2 flat public $worldGamemode; public $difficulty; public $spawnX; public $spawnY; public $spawnZ; public $hasAchievementsDisabled = 1; public $dayCycleStopTime = -1; //-1 = not stopped, any positive value = stopped at that time public $eduMode = 0; public $rainLevel; public $lightningLevel; public $commandsEnabled; public $isTexturePacksRequired = 0; public $levelId = ""; public $worldName; public $premiumWorldTemplateId = ""; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityUniqueId); //EntityUniqueID $this->putEntityId($this->entityRuntimeId); //EntityRuntimeID $this->putVarInt($this->playerGamemode); //client gamemode, other field is world gamemode $this->putVector3f($this->x, $this->y, $this->z); $this->putLFloat($this->pitch); $this->putLFloat($this->yaw); $this->putVarInt($this->seed); $this->putVarInt($this->dimension); $this->putVarInt($this->generator); $this->putVarInt($this->worldGamemode); $this->putVarInt($this->difficulty); $this->putBlockCoords($this->spawnX, $this->spawnY, $this->spawnZ); $this->putBool($this->hasAchievementsDisabled); $this->putVarInt($this->dayCycleStopTime); $this->putBool($this->eduMode); $this->putLFloat($this->rainLevel); $this->putLFloat($this->lightningLevel); $this->putBool($this->commandsEnabled); $this->putBool($this->isTexturePacksRequired); $this->putUnsignedVarInt(0); //TODO: gamerules $this->putString($this->levelId); $this->putString($this->worldName); $this->putString($this->premiumWorldTemplateId); } } class StopSoundPacket extends DataPacket { const NETWORK_ID = Info::STOP_SOUND_PACKET; public $sound; public $stopAll; /** * */ public function decode(){ $this->sound = $this->getString(); $this->stopAll = $this->getBool(); } /** * */ public function encode(){ $this->reset(); $this->putString($this->sound); $this->putBool($this->stopAll); } } class TakeItemEntityPacket extends DataPacket { const NETWORK_ID = Info::TAKE_ITEM_ENTITY_PACKET; public $target; public $eid; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->target); $this->putEntityId($this->eid); } /** * @return PacketName|string */ public function getName(){ return "TakeItemEntityPacket"; } } class TextPacket extends DataPacket { const NETWORK_ID = Info::TEXT_PACKET; const TYPE_RAW = 0; const TYPE_CHAT = 1; const TYPE_TRANSLATION = 2; const TYPE_POPUP = 3; const TYPE_TIP = 4; const TYPE_SYSTEM = 5; const TYPE_WHISPER = 6; public $type; public $source; public $message; public $parameters = []; /** * */ public function decode(){ $this->type = $this->getByte(); switch($this->type){ case self::TYPE_POPUP: case self::TYPE_CHAT: /** @noinspection PhpMissingBreakStatementInspection */ case self::TYPE_WHISPER: $this->source = $this->getString(); case self::TYPE_RAW: case self::TYPE_TIP: case self::TYPE_SYSTEM: $this->message = $this->getString(); break; case self::TYPE_TRANSLATION: $this->message = $this->getString(); $count = $this->getUnsignedVarInt(); for($i = 0; $i < $count; ++$i){ $this->parameters[] = $this->getString(); } } } /** * */ public function encode(){ $this->reset(); $this->putByte($this->type); switch($this->type){ case self::TYPE_POPUP: case self::TYPE_CHAT: /** @noinspection PhpMissingBreakStatementInspection */ case self::TYPE_WHISPER: $this->putString($this->source); case self::TYPE_RAW: case self::TYPE_TIP: case self::TYPE_SYSTEM: $this->putString($this->message); break; case self::TYPE_TRANSLATION: $this->putString($this->message); $this->putUnsignedVarInt(count($this->parameters)); foreach($this->parameters as $p){ $this->putString($p); } } } /** * @return PacketName|string */ public function getName(){ return "TextPacket"; } } reset(); $this->putString($this->address); $this->putLShort($this->port); } } use pocketmine\entity\Attribute; class UpdateAttributesPacket extends DataPacket { const NETWORK_ID = Info::UPDATE_ATTRIBUTES_PACKET; public $entityId; /** @var Attribute[] */ public $entries = []; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putEntityId($this->entityId); $this->putUnsignedVarInt(count($this->entries)); foreach($this->entries as $entry){ $this->putLFloat($entry->getMinValue()); $this->putLFloat($entry->getMaxValue()); $this->putLFloat($entry->getValue()); $this->putLFloat($entry->getDefaultValue()); $this->putString($entry->getName()); } } /** * @return PacketName|string */ public function getName(){ return "UpdateAttributesPacket"; } } class UpdateBlockPacket extends DataPacket { const NETWORK_ID = Info::UPDATE_BLOCK_PACKET; const FLAG_NONE = 0b0000; const FLAG_NEIGHBORS = 0b0001; const FLAG_NETWORK = 0b0010; const FLAG_NOGRAPHIC = 0b0100; const FLAG_PRIORITY = 0b1000; const FLAG_ALL = (self::FLAG_NEIGHBORS | self::FLAG_NETWORK); const FLAG_ALL_PRIORITY = (self::FLAG_ALL | self::FLAG_PRIORITY); public $x; public $z; public $y; public $blockId; public $blockData; public $flags; /** * */ public function decode(){ } /** * */ public function encode(){ $this->reset(); $this->putBlockCoords($this->x, $this->y, $this->z); $this->putUnsignedVarInt($this->blockId); $this->putUnsignedVarInt(($this->flags << 4) | $this->blockData); } /** * @return PacketName|string */ public function getName(){ return "UpdateBlockPacket"; } }byte1 = $this->getByte(); $this->byte2 = $this->getByte(); $this->varint1 = $this->getVarInt(); $this->varint2 = $this->getVarInt(); $this->isWilling = $this->getBool(); $this->traderEid = $this->getEntityId(); $this->playerEid = $this->getEntityId(); $this->displayName = $this->getString(); $this->offers = $this->get(true); } /** * */ public function encode(){ $this->reset(); $this->putByte($this->byte1); $this->putByte($this->byte2); $this->putVarInt($this->varint1); $this->putVarInt($this->varint2); $this->putBool($this->isWilling); $this->putEntityId($this->traderEid); $this->putEntityId($this->playerEid); $this->putString($this->displayName); $this->put($this->offers); } } class UseItemPacket extends DataPacket { const NETWORK_ID = Info::USE_ITEM_PACKET; public $x; public $y; public $z; public $blockId; public $face; public $item; public $fx; public $fy; public $fz; public $posX; public $posY; public $posZ; public $slot; /** * */ public function decode(){ $this->getBlockCoords($this->x, $this->y, $this->z); $this->blockId = $this->getUnsignedVarInt(); $this->face = $this->getVarInt(); $this->getVector3f($this->fx, $this->fy, $this->fz); $this->getVector3f($this->posX, $this->posY, $this->posZ); $this->slot = $this->getVarInt(); $this->item = $this->getSlot(); } /** * */ public function encode(){ } /** * @return PacketName|string */ public function getName(){ return "UseItemPacket"; } } server = Server::getInstance(); $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.server.query.start")); $addr = ($ip = $this->server->getIp()) != "" ? $ip : "0.0.0.0"; $port = $this->server->getPort(); $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.server.query.info", [$port])); /* The Query protocol is built on top of the existing Minecraft PE UDP network stack. Because the 0xFE packet does not exist in the MCPE protocol, we can identify Query packets and remove them from the packet queue. Then, the Query class handles itself sending the packets in raw form, because packets can conflict with the MCPE ones. */ $this->regenerateToken(); $this->lastToken = $this->token; $this->regenerateInfo(); $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.server.query.running", [$addr, $port])); } public function regenerateInfo(){ $ev = $this->server->getQueryInformation(); $this->longData = $ev->getLongQuery(); $this->shortData = $ev->getShortQuery(); $this->timeout = microtime(true) + $ev->getTimeout(); } public function regenerateToken(){ $this->lastToken = $this->token; $this->token = random_bytes(16); } /** * @param $token * @param $salt * * @return int */ public static function getTokenString($token, $salt){ return Binary::readInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4)); } /** * @param $address * @param $port * @param $packet */ public function handle($address, $port, $packet){ $offset = 2; $packetType = ord($packet{$offset++}); $sessionID = Binary::readInt(substr($packet, $offset, 4)); $offset += 4; $payload = substr($packet, $offset); switch($packetType){ case self::HANDSHAKE: //Handshake $reply = chr(self::HANDSHAKE); $reply .= Binary::writeInt($sessionID); $reply .= self::getTokenString($this->token, $address) . "\x00"; $this->server->getNetwork()->sendPacket($address, $port, $reply); break; case self::STATISTICS: //Stat $token = Binary::readInt(substr($payload, 0, 4)); if($token !== self::getTokenString($this->token, $address) and $token !== self::getTokenString($this->lastToken, $address)){ break; } $reply = chr(self::STATISTICS); $reply .= Binary::writeInt($sessionID); if($this->timeout < microtime(true)){ $this->regenerateInfo(); } if(strlen($payload) === 8){ $reply .= $this->longData; }else{ $reply .= $this->shortData; } $this->server->getNetwork()->sendPacket($address, $port, $reply); break; } } } server = $server; $this->workers = []; $this->password = (string) $password; $this->server->getLogger()->info("Starting remote control listener"); if($this->password === ""){ $this->server->getLogger()->critical("RCON can't be started: Empty password"); return; } $this->threads = (int) max(1, $threads); $this->clientsPerThread = (int) max(1, $clientsPerThread); $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if($this->socket === false or !socket_bind($this->socket, $interface, (int) $port) or !socket_listen($this->socket)){ $this->server->getLogger()->critical("RCON can't be started: " . socket_strerror(socket_last_error())); $this->threads = 0; return; } socket_set_block($this->socket); for($n = 0; $n < $this->threads; ++$n){ $this->workers[$n] = new RCONInstance($this->server->getLogger(), $this->socket, $this->password, $this->clientsPerThread); } socket_getsockname($this->socket, $addr, $port); $this->server->getLogger()->info("RCON running on $addr:$port"); } public function stop(){ for($n = 0; $n < $this->threads; ++$n){ $this->workers[$n]->close(); Server::microSleep(50000); $this->workers[$n]->close(); $this->workers[$n]->quit(); } @socket_close($this->socket); $this->threads = 0; } public function check(){ $d = Utils::getRealMemoryUsage(); $u = Utils::getMemoryUsage(true); $usage = round(($u[0] / 1024) / 1024, 2) . "/" . round(($d[0] / 1024) / 1024, 2) . "/" . round(($u[1] / 1024) / 1024, 2) . "/" . round(($u[2] / 1024) / 1024, 2) . " MB @ " . Utils::getThreadCount() . " threads"; $serverStatus = serialize([ "online" => count($this->server->getOnlinePlayers()), "max" => $this->server->getMaxPlayers(), "upload" => round($this->server->getNetwork()->getUpload() / 1024, 2), "download" => round($this->server->getNetwork()->getDownload() / 1024, 2), "tps" => $this->server->getTicksPerSecondAverage(), "load" => $this->server->getTickUsageAverage(), "usage" => $usage ]); for($n = 0; $n < $this->threads; ++$n){ if(!$this->workers[$n]->isTerminated()){ $this->workers[$n]->serverStatus = $serverStatus; } if($this->workers[$n]->isTerminated() === true){ $this->workers[$n] = new RCONInstance($this->socket, $this->password, $this->clientsPerThread); }elseif($this->workers[$n]->isWaiting()){ if($this->workers[$n]->response !== ""){ $this->server->getLogger()->info($this->workers[$n]->response); $this->workers[$n]->synchronized(function(RCONInstance $thread){ $thread->notify(); }, $this->workers[$n]); }else{ $response = new RemoteConsoleCommandSender(); $command = $this->workers[$n]->cmd; $this->server->getPluginManager()->callEvent($ev = new RemoteServerCommandEvent($response, $command)); if(!$ev->isCancelled()){ $this->server->dispatchCommand($ev->getSender(), $ev->getCommand()); } $this->workers[$n]->response = $response->getMessage(); $this->workers[$n]->synchronized(function(RCONInstance $thread){ $thread->notify(); }, $this->workers[$n]); } } } } } waiting === true; } /** * RCONInstance constructor. * * @param $logger * @param $socket * @param $password * @param int $maxClients */ public function __construct($logger, $socket, $password, $maxClients = 50){ $this->logger = $logger; $this->stop = false; $this->cmd = ""; $this->response = ""; $this->socket = $socket; $this->password = $password; $this->maxClients = (int) $maxClients; for($n = 0; $n < $this->maxClients; ++$n){ $this->{"client" . $n} = null; $this->{"status" . $n} = 0; $this->{"timeout" . $n} = 0; } $this->start(); } /** * @param $client * @param $requestID * @param $packetType * @param $payload * * @return int */ private function writePacket($client, $requestID, $packetType, $payload){ $pk = Binary::writeLInt((int) $requestID) . Binary::writeLInt((int) $packetType) . $payload . "\x00\x00"; //Terminate payload and packet return socket_write($client, Binary::writeLInt(strlen($pk)) . $pk); } /** * @param $client * @param $size * @param $requestID * @param $packetType * @param $payload * * @return bool|null */ private function readPacket($client, &$size, &$requestID, &$packetType, &$payload){ socket_set_nonblock($client); $d = @socket_read($client, 4); if($this->stop === true){ return false; }elseif($d === false){ return null; }elseif($d === "" or strlen($d) < 4){ return false; } socket_set_block($client); $size = Binary::readLInt($d); if($size < 0 or $size > 65535){ return false; } $requestID = Binary::readLInt(socket_read($client, 4)); $packetType = Binary::readLInt(socket_read($client, 4)); $payload = rtrim(socket_read($client, $size + 2)); //Strip two null bytes return true; } public function close(){ $this->stop = true; } public function run(){ while($this->stop !== true){ $this->synchronized(function(){ $this->wait(2000); }); $r = [$socket = $this->socket]; $w = null; $e = null; if(socket_select($r, $w, $e, 0) === 1){ if(($client = socket_accept($this->socket)) !== false){ socket_set_block($client); socket_set_option($client, SOL_SOCKET, SO_KEEPALIVE, 1); $done = false; for($n = 0; $n < $this->maxClients; ++$n){ if($this->{"client" . $n} === null){ $this->{"client" . $n} = $client; $this->{"status" . $n} = 0; $this->{"timeout" . $n} = microtime(true) + 5; $done = true; break; } } if($done === false){ @socket_close($client); } } } for($n = 0; $n < $this->maxClients; ++$n){ $client = &$this->{"client" . $n}; if($client !== null){ if($this->{"status" . $n} !== -1 and $this->stop !== true){ if($this->{"status" . $n} === 0 and $this->{"timeout" . $n} < microtime(true)){ //Timeout $this->{"status" . $n} = -1; continue; } $p = $this->readPacket($client, $size, $requestID, $packetType, $payload); if($p === false){ $this->{"status" . $n} = -1; continue; }elseif($p === null){ continue; } switch($packetType){ case 9: //Protocol check if($this->{"status" . $n} !== 1){ $this->{"status" . $n} = -1; continue; } $this->writePacket($client, $requestID, 0, RCON::PROTOCOL_VERSION); $this->response = ""; if($payload == RCON::PROTOCOL_VERSION) $this->logger->setSendMsg(true); //GeniRCON output break; case 4: //Logger if($this->{"status" . $n} !== 1){ $this->{"status" . $n} = -1; continue; } $res = (array) [ "serverStatus" => unserialize($this->serverStatus), "logger" => str_replace("\n", "\r\n", trim($this->logger->getMessages())) ]; $this->writePacket($client, $requestID, 0, serialize($res)); $this->response = ""; break; case 3: //Login if($this->{"status" . $n} !== 0){ $this->{"status" . $n} = -1; continue; } if($payload === $this->password){ socket_getpeername($client, $addr, $port); $this->response = "[INFO] Successful Rcon connection from: /$addr:$port"; $this->response = ""; $this->writePacket($client, $requestID, 2, ""); $this->{"status" . $n} = 1; }else{ $this->{"status" . $n} = -1; $this->writePacket($client, -1, 2, ""); continue; } break; case 2: //Command if($this->{"status" . $n} !== 1){ $this->{"status" . $n} = -1; continue; } if(strlen($payload) > 0){ $this->cmd = ltrim($payload); $this->synchronized(function(){ $this->waiting = true; $this->wait(); }); $this->waiting = false; $this->writePacket($client, $requestID, 0, str_replace("\n", "\r\n", trim($this->response))); $this->response = ""; $this->cmd = ""; } break; } }else{ @socket_set_option($client, SOL_SOCKET, SO_LINGER, ["l_onoff" => 1, "l_linger" => 1]); @socket_shutdown($client, 2); @socket_set_block($client); @socket_read($client, 1); @socket_close($client); $this->{"status" . $n} = 0; $this->{"client" . $n} = null; } } } } unset($this->socket, $this->cmd, $this->response, $this->stop); exit(0); } /** * @return string */ public function getThreadName(){ return "RCON"; } }StaticPortMappingCollection)){ return false; } $com->StaticPortMappingCollection->Add($port, "UDP", $port, $myLocalIP, true, "PocketMine-MP"); }catch(\Throwable $e){ return false; } return true; } /** * @param $port * * @return bool */ public static function RemovePortForward($port){ if(Utils::$online === false){ return false; } if(Utils::getOS() != "win" or !class_exists("COM")){ return false; } $port = (int) $port; try{ $com = new \COM("HNetCfg.NATUPnP") or false; if($com === false or !is_object($com->StaticPortMappingCollection)){ return false; } $com->StaticPortMappingCollection->Remove($port, "UDP"); }catch(\Throwable $e){ return false; } return true; } } name = strtolower($name); $this->creationDate = new \DateTime(); } /** * @return string */ public function getName() : string{ return $this->name; } /** * @return \DateTime */ public function getCreated(){ return $this->creationDate; } /** * @param \DateTime $date */ public function setCreated(\DateTime $date){ $this->creationDate = $date; } /** * @return string */ public function getSource(){ return $this->source; } /** * @param $source */ public function setSource($source){ $this->source = $source; } /** * @return \DateTime */ public function getExpires(){ return $this->expirationDate; } /** * @param \DateTime $date */ public function setExpires($date){ $this->expirationDate = $date; } /** * @return bool */ public function hasExpired(){ $now = new \DateTime(); return $this->expirationDate === null ? false : $this->expirationDate < $now; } /** * @return string */ public function getReason(){ return $this->reason; } /** * @param $reason */ public function setReason($reason){ $this->reason = $reason; } /** * @return string */ public function getString(){ $str = ""; $str .= $this->getName(); $str .= "|"; $str .= $this->getCreated()->format(self::$format); $str .= "|"; $str .= $this->getSource(); $str .= "|"; $str .= $this->getExpires() === null ? "Forever" : $this->getExpires()->format(self::$format); $str .= "|"; $str .= $this->getReason(); return $str; } /** * @param string $str * * @return BanEntry */ public static function fromString($str){ if(strlen($str) < 2){ return null; }else{ $str = explode("|", trim($str)); $entry = new BanEntry(trim(array_shift($str))); if(count($str) > 0){ $datetime = \DateTime::createFromFormat(self::$format, array_shift($str)); if(!($datetime instanceof \DateTime)){ MainLogger::getLogger()->alert("Error parsing date for BanEntry for player \"" . $entry->getName() . "\", the format may be invalid!"); return $entry; } $entry->setCreated($datetime); if(count($str) > 0){ $entry->setSource(trim(array_shift($str))); if(count($str) > 0){ $expire = trim(array_shift($str)); if(strtolower($expire) !== "forever" and strlen($expire) > 0){ $entry->setExpires(\DateTime::createFromFormat(self::$format, $expire)); } if(count($str) > 0){ $entry->setReason(trim(array_shift($str))); } } } } return $entry; } } }file = $file; } /** * @return bool */ public function isEnabled(){ return $this->enabled === true; } /** * @param bool $flag */ public function setEnabled($flag){ $this->enabled = (bool) $flag; } /** * @return BanEntry[] */ public function getEntries(){ $this->removeExpired(); return $this->list; } /** * @param string $name * * @return bool */ public function isBanned($name){ $name = strtolower($name); if(!$this->isEnabled()){ return false; }else{ $this->removeExpired(); return isset($this->list[$name]); } } /** * @param BanEntry $entry */ public function add(BanEntry $entry){ $this->list[$entry->getName()] = $entry; $this->save(); } /** * @param string $target * @param string $reason * @param \DateTime $expires * @param string $source * * @return BanEntry */ public function addBan($target, $reason = null, $expires = null, $source = null){ $entry = new BanEntry($target); $entry->setSource($source != null ? $source : $entry->getSource()); $entry->setExpires($expires); $entry->setReason($reason != null ? $reason : $entry->getReason()); $this->list[$entry->getName()] = $entry; $this->save(); return $entry; } /** * @param string $name */ public function remove($name){ $name = strtolower($name); if(isset($this->list[$name])){ unset($this->list[$name]); $this->save(); } } public function removeExpired(){ foreach($this->list as $name => $entry){ if($entry->hasExpired()){ unset($this->list[$name]); } } } public function load(){ $this->list = []; $fp = @fopen($this->file, "r"); if(is_resource($fp)){ while(($line = fgets($fp)) !== false){ if($line{0} !== "#"){ $entry = BanEntry::fromString($line); if($entry instanceof BanEntry){ $this->list[$entry->getName()] = $entry; } } } fclose($fp); }else{ MainLogger::getLogger()->error("Could not load ban list"); } } /** * @param bool $flag */ public function save($flag = true){ $this->removeExpired(); $fp = @fopen($this->file, "w"); if(is_resource($fp)){ if($flag === true){ fwrite($fp, "# Updated " . strftime("%x %H:%M", time()) . " by " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion() . "\n"); fwrite($fp, "# victim name | ban date | banned by | banned until | reason\n\n"); } foreach($this->list as $entry){ fwrite($fp, $entry->getString() . "\n"); } fclose($fp); }else{ MainLogger::getLogger()->error("Could not save ban list"); } } }getChildren()[$perm->getName()] = true; return self::registerPermission($perm); } Server::getInstance()->getPluginManager()->addPermission($perm); return Server::getInstance()->getPluginManager()->getPermission($perm->getName()); } public static function registerCorePermissions(){ $parent = self::registerPermission(new Permission(self::ROOT, "Allows using all PocketMine commands and utilities")); $broadcasts = self::registerPermission(new Permission(self::ROOT . ".broadcast", "Allows the user to receive all broadcast messages"), $parent); self::registerPermission(new Permission(self::ROOT . ".broadcast.admin", "Allows the user to receive administrative broadcasts", Permission::DEFAULT_OP), $broadcasts); self::registerPermission(new Permission(self::ROOT . ".broadcast.user", "Allows the user to receive user broadcasts", Permission::DEFAULT_TRUE), $broadcasts); $broadcasts->recalculatePermissibles(); $commands = self::registerPermission(new Permission(self::ROOT . ".command", "Allows using all PocketMine commands"), $parent); $whitelist = self::registerPermission(new Permission(self::ROOT . ".command.whitelist", "Allows the user to modify the server whitelist", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.whitelist.add", "Allows the user to add a player to the server whitelist"), $whitelist); self::registerPermission(new Permission(self::ROOT . ".command.whitelist.remove", "Allows the user to remove a player to the server whitelist"), $whitelist); self::registerPermission(new Permission(self::ROOT . ".command.whitelist.reload", "Allows the user to reload the server whitelist"), $whitelist); self::registerPermission(new Permission(self::ROOT . ".command.whitelist.enable", "Allows the user to enable the server whitelist"), $whitelist); self::registerPermission(new Permission(self::ROOT . ".command.whitelist.disable", "Allows the user to disable the server whitelist"), $whitelist); self::registerPermission(new Permission(self::ROOT . ".command.whitelist.list", "Allows the user to list all the players on the server whitelist"), $whitelist); $whitelist->recalculatePermissibles(); $ban = self::registerPermission(new Permission(self::ROOT . ".command.ban", "Allows the user to ban people", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.ban.player", "Allows the user to ban players"), $ban); self::registerPermission(new Permission(self::ROOT . ".command.ban.ip", "Allows the user to ban IP addresses"), $ban); $ban->recalculatePermissibles(); $unban = self::registerPermission(new Permission(self::ROOT . ".command.unban", "Allows the user to unban people", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.unban.player", "Allows the user to unban players"), $unban); self::registerPermission(new Permission(self::ROOT . ".command.unban.ip", "Allows the user to unban IP addresses"), $unban); $unban->recalculatePermissibles(); $op = self::registerPermission(new Permission(self::ROOT . ".command.op", "Allows the user to change operators", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.op.give", "Allows the user to give a player operator status"), $op); self::registerPermission(new Permission(self::ROOT . ".command.op.take", "Allows the user to take a players operator status"), $op); $op->recalculatePermissibles(); $save = self::registerPermission(new Permission(self::ROOT . ".command.save", "Allows the user to save the worlds", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.save.enable", "Allows the user to enable automatic saving"), $save); self::registerPermission(new Permission(self::ROOT . ".command.save.disable", "Allows the user to disable automatic saving"), $save); self::registerPermission(new Permission(self::ROOT . ".command.save.perform", "Allows the user to perform a manual save"), $save); $save->recalculatePermissibles(); $time = self::registerPermission(new Permission(self::ROOT . ".command.time", "Allows the user to alter the time", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.time.add", "Allows the user to fast-forward time"), $time); self::registerPermission(new Permission(self::ROOT . ".command.time.set", "Allows the user to change the time"), $time); self::registerPermission(new Permission(self::ROOT . ".command.time.start", "Allows the user to restart the time"), $time); self::registerPermission(new Permission(self::ROOT . ".command.time.stop", "Allows the user to stop the time"), $time); self::registerPermission(new Permission(self::ROOT . ".command.time.query", "Allows the user query the time"), $time); $time->recalculatePermissibles(); $kill = self::registerPermission(new Permission(self::ROOT . ".command.kill", "Allows the user to kill players", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.kill.self", "Allows the user to commit suicide", Permission::DEFAULT_TRUE), $kill); self::registerPermission(new Permission(self::ROOT . ".command.kill.other", "Allows the user to kill other players"), $kill); $kill->recalculatePermissibles(); self::registerPermission(new Permission(self::ROOT . ".command.me", "Allows the user to perform a chat action", Permission::DEFAULT_TRUE), $commands); self::registerPermission(new Permission(self::ROOT . ".command.tell", "Allows the user to privately message another player", Permission::DEFAULT_TRUE), $commands); self::registerPermission(new Permission(self::ROOT . ".command.say", "Allows the user to talk as the console", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.give", "Allows the user to give items to players", Permission::DEFAULT_OP), $commands); $effect = self::registerPermission(new Permission(self::ROOT . ".command.effect", "Allows the user to give/take potion effects", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.effect.other", "Allows the user to give/take potion effects for other", Permission::DEFAULT_OP), $commands); $effect->recalculatePermissibles(); self::registerPermission(new Permission(self::ROOT . ".command.enchant", "Allows the user to enchant items", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.particle", "Allows the user to create particle effects", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.teleport", "Allows the user to teleport players", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.kick", "Allows the user to kick players", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.stop", "Allows the user to stop the server", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.list", "Allows the user to list all online players", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.help", "Allows the user to view the help menu", Permission::DEFAULT_TRUE), $commands); self::registerPermission(new Permission(self::ROOT . ".command.plugins", "Allows the user to view the list of plugins", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.reload", "Allows the user to reload the server settings", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.version", "Allows the user to view the version of the server", Permission::DEFAULT_TRUE), $commands); self::registerPermission(new Permission(self::ROOT . ".command.gamemode", "Allows the user to change the gamemode of players", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.defaultgamemode", "Allows the user to change the default gamemode", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.seed", "Allows the user to view the seed of the world", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.status", "Allows the user to view the server performance", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.gc", "Allows the user to fire garbage collection tasks", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.dumpmemory", "Allows the user to dump memory contents", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.timings", "Allows the user to records timings for all plugin events", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.spawnpoint", "Allows the user to change player's spawnpoint", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.setworldspawn", "Allows the user to change the world spawn", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.extractphar", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.extractplugin", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.makeplugin", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.makeserver", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.loadplugin", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.bancid", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.pardoncid", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.bancidbyname", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.banipbyname", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.weather", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.loadplugin", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.lvdat", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.biome", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.cave", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.setblock", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.fill", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.summon", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.xp", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.chunkinfo", "", Permission::DEFAULT_OP), $commands); self::registerPermission(new Permission(self::ROOT . ".command.transfer", "", Permission::DEFAULT_OP), $commands); $commands->recalculatePermissibles(); $parent->recalculatePermissibles(); } } opable = $opable; if($opable instanceof Permissible){ $this->parent = $opable; } } public function __destruct(){ $this->parent = null; $this->opable = null; } /** * @return bool */ public function isOp(){ if($this->opable === null){ return false; }else{ return $this->opable->isOp(); } } /** * @param bool $value * * @throws \Throwable */ public function setOp($value){ if($this->opable === null){ throw new \LogicException("Cannot change op value as no ServerOperator is set"); }else{ $this->opable->setOp($value); } } /** * @param Permission|string $name * * @return bool */ public function isPermissionSet($name){ return isset($this->permissions[$name instanceof Permission ? $name->getName() : $name]); } /** * @param Permission|string $name * * @return bool */ public function hasPermission($name){ if($name instanceof Permission){ $name = $name->getName(); } if($this->isPermissionSet($name)){ return $this->permissions[$name]->getValue(); } if(($perm = Server::getInstance()->getPluginManager()->getPermission($name)) !== null){ $perm = $perm->getDefault(); return $perm === Permission::DEFAULT_TRUE or ($this->isOp() and $perm === Permission::DEFAULT_OP) or (!$this->isOp() and $perm === Permission::DEFAULT_NOT_OP); }else{ return Permission::$DEFAULT_PERMISSION === Permission::DEFAULT_TRUE or ($this->isOp() and Permission::$DEFAULT_PERMISSION === Permission::DEFAULT_OP) or (!$this->isOp() and Permission::$DEFAULT_PERMISSION === Permission::DEFAULT_NOT_OP); } } /** * //TODO: tick scheduled attachments * * @param Plugin $plugin * @param string $name * @param bool $value * * @return PermissionAttachment * * @throws PluginException */ public function addAttachment(Plugin $plugin, $name = null, $value = null){ if($plugin === null){ throw new PluginException("Plugin cannot be null"); }elseif(!$plugin->isEnabled()){ throw new PluginException("Plugin " . $plugin->getDescription()->getName() . " is disabled"); } $result = new PermissionAttachment($plugin, $this->parent !== null ? $this->parent : $this); $this->attachments[spl_object_hash($result)] = $result; if($name !== null and $value !== null){ $result->setPermission($name, $value); } $this->recalculatePermissions(); return $result; } /** * @param PermissionAttachment $attachment * * @throws \Throwable */ public function removeAttachment(PermissionAttachment $attachment){ if($attachment === null){ throw new \InvalidStateException("Attachment cannot be null"); } if(isset($this->attachments[spl_object_hash($attachment)])){ unset($this->attachments[spl_object_hash($attachment)]); if(($ex = $attachment->getRemovalCallback()) !== null){ $ex->attachmentRemoved($attachment); } $this->recalculatePermissions(); } } public function recalculatePermissions(){ Timings::$permissibleCalculationTimer->startTiming(); $this->clearPermissions(); $defaults = Server::getInstance()->getPluginManager()->getDefaultPermissions($this->isOp()); Server::getInstance()->getPluginManager()->subscribeToDefaultPerms($this->isOp(), $this->parent !== null ? $this->parent : $this); foreach($defaults as $perm){ $name = $perm->getName(); $this->permissions[$name] = new PermissionAttachmentInfo($this->parent !== null ? $this->parent : $this, $name, null, true); Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent !== null ? $this->parent : $this); $this->calculateChildPermissions($perm->getChildren(), false, null); } foreach($this->attachments as $attachment){ $this->calculateChildPermissions($attachment->getPermissions(), false, $attachment); } Timings::$permissibleCalculationTimer->stopTiming(); } public function clearPermissions(){ foreach(array_keys($this->permissions) as $name){ Server::getInstance()->getPluginManager()->unsubscribeFromPermission($name, $this->parent !== null ? $this->parent : $this); } Server::getInstance()->getPluginManager()->unsubscribeFromDefaultPerms(false, $this->parent !== null ? $this->parent : $this); Server::getInstance()->getPluginManager()->unsubscribeFromDefaultPerms(true, $this->parent !== null ? $this->parent : $this); $this->permissions = []; } /** * @param bool[] $children * @param bool $invert * @param PermissionAttachment $attachment */ private function calculateChildPermissions(array $children, $invert, $attachment){ foreach($children as $name => $v){ $perm = Server::getInstance()->getPluginManager()->getPermission($name); $value = ($v xor $invert); $this->permissions[$name] = new PermissionAttachmentInfo($this->parent !== null ? $this->parent : $this, $name, $attachment, $value); Server::getInstance()->getPluginManager()->subscribeToPermission($name, $this->parent !== null ? $this->parent : $this); if($perm instanceof Permission){ $this->calculateChildPermissions($perm->getChildren(), !$value, $attachment); } } } /** * @return PermissionAttachmentInfo[] */ public function getEffectivePermissions(){ return $this->permissions; } } name = $name; $this->description = $description !== null ? $description : ""; $this->defaultValue = $defaultValue !== null ? $defaultValue : self::$DEFAULT_PERMISSION; $this->children = $children; $this->recalculatePermissibles(); } /** * @return string */ public function getName() : string{ return $this->name; } /** * @return string[] */ public function &getChildren(){ return $this->children; } /** * @return string */ public function getDefault(){ return $this->defaultValue; } /** * @param string $value */ public function setDefault($value){ if($value !== $this->defaultValue){ $this->defaultValue = $value; $this->recalculatePermissibles(); } } /** * @return string */ public function getDescription(){ return $this->description; } /** * @param string $value */ public function setDescription($value){ $this->description = $value; } /** * @return Permissible[] */ public function getPermissibles(){ return Server::getInstance()->getPluginManager()->getPermissionSubscriptions($this->name); } public function recalculatePermissibles(){ $perms = $this->getPermissibles(); Server::getInstance()->getPluginManager()->recalculatePermissionDefaults($this); foreach($perms as $p){ $p->recalculatePermissions(); } } /** * @param string|Permission $name * @param $value * * @return Permission|null Permission if $name is a string, void if it's a Permission */ public function addParent($name, $value){ if($name instanceof Permission){ $name->getChildren()[$this->getName()] = $value; $name->recalculatePermissibles(); return null; }else{ $perm = Server::getInstance()->getPluginManager()->getPermission($name); if($perm === null){ $perm = new Permission($name); Server::getInstance()->getPluginManager()->addPermission($perm); } $this->addParent($perm, $value); return $perm; } } /** * @param array $data * @param $default * * @return Permission[] */ public static function loadPermissions(array $data, $default = self::DEFAULT_OP){ $result = []; foreach($data as $key => $entry){ $result[] = self::loadPermission($key, $entry, $default, $result); } return $result; } /** * @param string $name * @param array $data * @param string $default * @param array $output * * @return Permission * * @throws \Throwable */ public static function loadPermission($name, array $data, $default = self::DEFAULT_OP, &$output = []){ $desc = null; $children = []; if(isset($data["default"])){ $value = Permission::getByName($data["default"]); if($value !== null){ $default = $value; }else{ throw new \InvalidStateException("'default' key contained unknown value"); } } if(isset($data["children"])){ if(is_array($data["children"])){ foreach($data["children"] as $k => $v){ if(is_array($v)){ if(($perm = self::loadPermission($k, $v, $default, $output)) !== null){ $output[] = $perm; } } $children[$k] = true; } }else{ throw new \InvalidStateException("'children' key is of wrong type"); } } if(isset($data["description"])){ $desc = $data["description"]; } return new Permission($name, $desc, $default, $children); } }isEnabled()){ throw new PluginException("Plugin " . $plugin->getDescription()->getName() . " is disabled"); } $this->permissible = $permissible; $this->plugin = $plugin; } /** * @return Plugin */ public function getPlugin(){ return $this->plugin; } /** * @param PermissionRemovedExecutor $ex */ public function setRemovalCallback(PermissionRemovedExecutor $ex){ $this->removed = $ex; } /** * @return PermissionRemovedExecutor */ public function getRemovalCallback(){ return $this->removed; } /** * @return Permissible */ public function getPermissible(){ return $this->permissible; } /** * @return bool[] */ public function getPermissions(){ return $this->permissions; } public function clearPermissions(){ $this->permissions = []; $this->permissible->recalculatePermissions(); } /** * @param bool[] $permissions */ public function setPermissions(array $permissions){ foreach($permissions as $key => $value){ $this->permissions[$key] = (bool) $value; } $this->permissible->recalculatePermissions(); } /** * @param string[] $permissions */ public function unsetPermissions(array $permissions){ foreach($permissions as $node){ unset($this->permissions[$node]); } $this->permissible->recalculatePermissions(); } /** * @param string|Permission $name * @param bool $value */ public function setPermission($name, $value){ $name = $name instanceof Permission ? $name->getName() : $name; if(isset($this->permissions[$name])){ if($this->permissions[$name] === $value){ return; } unset($this->permissions[$name]); //Fixes children getting overwritten } $this->permissions[$name] = $value; $this->permissible->recalculatePermissions(); } /** * @param string|Permission $name */ public function unsetPermission($name){ $name = $name instanceof Permission ? $name->getName() : $name; if(isset($this->permissions[$name])){ unset($this->permissions[$name]); $this->permissible->recalculatePermissions(); } } /** * @return void */ public function remove(){ $this->permissible->removeAttachment($this); } }permissible = $permissible; $this->permission = $permission; $this->attachment = $attachment; $this->value = $value; } /** * @return Permissible */ public function getPermissible(){ return $this->permissible; } /** * @return string */ public function getPermission(){ return $this->permission; } /** * @return PermissionAttachment */ public function getAttachment(){ return $this->attachment; } /** * @return bool */ public function getValue(){ return $this->value; } }server = $server; } /** * Loads the plugin contained in $file * * @param string $file * * @return Plugin */ public function loadPlugin($file){ if(is_dir($file) and file_exists($file . "/plugin.yml") and file_exists($file . "/src/")){ if(($description = $this->getPluginDescription($file)) instanceof PluginDescription){ MainLogger::getLogger()->info(TextFormat::LIGHT_PURPLE . "Loading (Source) " . $description->getFullName()); $dataFolder = dirname($file) . DIRECTORY_SEPARATOR . $description->getName(); if(file_exists($dataFolder) and !is_dir($dataFolder)){ trigger_error("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory", E_USER_WARNING); return null; } $className = $description->getMain(); $this->server->getLoader()->addPath($file . "/src"); if(class_exists($className, true)){ $plugin = new $className(); $this->initPlugin($plugin, $description, $dataFolder, $file); return $plugin; }else{ trigger_error("Couldn't load source plugin " . $description->getName() . ": main class not found", E_USER_WARNING); return null; } } } return null; } /** * Gets the PluginDescription from the file * * @param string $file * * @return PluginDescription */ public function getPluginDescription($file){ if(is_dir($file) and file_exists($file . "/plugin.yml")){ $yaml = @file_get_contents($file . "/plugin.yml"); if($yaml != ""){ return new PluginDescription($yaml); } } return null; } /** * Returns the filename patterns that this loader accepts * * @return array|string */ public function getPluginFilters(){ return "/[^\\.]/"; } /** * @param PluginBase $plugin * @param PluginDescription $description * @param string $dataFolder * @param string $file */ private function initPlugin(PluginBase $plugin, PluginDescription $description, $dataFolder, $file){ $plugin->init($this, $this->server, $description, $dataFolder, $file); $plugin->onLoad(); } /** * @param Plugin $plugin */ public function enablePlugin(Plugin $plugin){ if($plugin instanceof PluginBase and !$plugin->isEnabled()){ MainLogger::getLogger()->info("Enabling " . $plugin->getDescription()->getFullName()); $plugin->setEnabled(true); Server::getInstance()->getPluginManager()->callEvent(new PluginEnableEvent($plugin)); } } /** * @param Plugin $plugin */ public function disablePlugin(Plugin $plugin){ if($plugin instanceof PluginBase and $plugin->isEnabled()){ MainLogger::getLogger()->info("Disabling " . $plugin->getDescription()->getFullName()); Server::getInstance()->getPluginManager()->callEvent(new PluginDisableEvent($plugin)); $plugin->setEnabled(false); } } } method = $method; } /** * @param Listener $listener * @param Event $event */ public function execute(Listener $listener, Event $event){ $listener->{$this->getMethod()}($event); } /** * @return mixed */ public function getMethod(){ return $this->method; } }server = $server; } /** * Loads the plugin contained in $file * * @param string $file * * @return Plugin * * @throws \Throwable */ public function loadPlugin($file){ if(($description = $this->getPluginDescription($file)) instanceof PluginDescription){ $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.load", [$description->getFullName()])); $dataFolder = dirname($file) . DIRECTORY_SEPARATOR . $description->getName(); if(file_exists($dataFolder) and !is_dir($dataFolder)){ throw new \InvalidStateException("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory"); } $file = "phar://$file"; $className = $description->getMain(); $this->server->getLoader()->addPath("$file/src"); if(class_exists($className, true)){ $plugin = new $className(); $this->initPlugin($plugin, $description, $dataFolder, $file); return $plugin; }else{ throw new PluginException("Couldn't load plugin " . $description->getName() . ": main class not found"); } } return null; } /** * Gets the PluginDescription from the file * * @param string $file * * @return PluginDescription */ public function getPluginDescription($file){ $phar = new \Phar($file); if(isset($phar["plugin.yml"])){ $pluginYml = $phar["plugin.yml"]; if($pluginYml instanceof \PharFileInfo){ return new PluginDescription($pluginYml->getContent()); } } return null; } /** * Returns the filename patterns that this loader accepts * * @return string */ public function getPluginFilters(){ return "/\\.phar$/i"; } /** * @param PluginBase $plugin * @param PluginDescription $description * @param string $dataFolder * @param string $file */ private function initPlugin(PluginBase $plugin, PluginDescription $description, $dataFolder, $file){ $plugin->init($this, $this->server, $description, $dataFolder, $file); $plugin->onLoad(); } /** * @param Plugin $plugin */ public function enablePlugin(Plugin $plugin){ if($plugin instanceof PluginBase and !$plugin->isEnabled()){ $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.enable", [$plugin->getDescription()->getFullName()])); $plugin->setEnabled(true); $this->server->getPluginManager()->callEvent(new PluginEnableEvent($plugin)); } } /** * @param Plugin $plugin */ public function disablePlugin(Plugin $plugin){ if($plugin instanceof PluginBase and $plugin->isEnabled()){ $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.disable", [$plugin->getDescription()->getFullName()])); $this->server->getPluginManager()->callEvent(new PluginDisableEvent($plugin)); $plugin->setEnabled(false); } } }isEnabled === true; } /** * @param bool $boolean */ public final function setEnabled($boolean = true){ if($this->isEnabled !== $boolean){ $this->isEnabled = $boolean; if($this->isEnabled === true){ $this->onEnable(); }else{ $this->onDisable(); } } } /** * @return bool */ public final function isDisabled(){ return $this->isEnabled === false; } /** * @return string */ public final function getDataFolder(){ return $this->dataFolder; } /** * @return PluginDescription */ public final function getDescription(){ return $this->description; } /** * @param PluginLoader $loader * @param Server $server * @param PluginDescription $description * @param $dataFolder * @param $file */ public final function init(PluginLoader $loader, Server $server, PluginDescription $description, $dataFolder, $file){ if($this->initialized === false){ $this->initialized = true; $this->loader = $loader; $this->server = $server; $this->description = $description; $this->dataFolder = rtrim($dataFolder, "\\/") . "/"; $this->file = rtrim($file, "\\/") . "/"; $this->configFile = $this->dataFolder . "config.yml"; $this->logger = new PluginLogger($this); } } /** * @return PluginLogger */ public function getLogger(){ return $this->logger; } /** * @return bool */ public final function isInitialized(){ return $this->initialized; } /** * @param string $name * * @return Command|PluginIdentifiableCommand */ public function getCommand($name){ $command = $this->getServer()->getPluginCommand($name); if($command === null or $command->getPlugin() !== $this){ $command = $this->getServer()->getPluginCommand(strtolower($this->description->getName()) . ":" . $name); } if($command instanceof PluginIdentifiableCommand and $command->getPlugin() === $this){ return $command; }else{ return null; } } /** * @param CommandSender $sender * @param Command $command * @param string $label * @param array $args * * @return bool */ public function onCommand(CommandSender $sender, Command $command, $label, array $args){ return false; } /** * @return bool */ protected function isPhar(){ return substr($this->file, 0, 7) === "phar://"; } /** * Gets an embedded resource on the plugin file. * WARNING: You must close the resource given using fclose() * * @param string $filename * * @return resource Resource data, or null */ public function getResource($filename){ $filename = rtrim(str_replace("\\", "/", $filename), "/"); if(file_exists($this->file . "resources/" . $filename)){ return fopen($this->file . "resources/" . $filename, "rb"); } return null; } /** * @param string $filename * @param bool $replace * * @return bool */ public function saveResource($filename, $replace = false){ if(trim($filename) === ""){ return false; } if(($resource = $this->getResource($filename)) === null){ return false; } $out = $this->dataFolder . $filename; if(!file_exists(dirname($out))){ mkdir(dirname($out), 0755, true); } if(file_exists($out) and $replace !== true){ return false; } $ret = stream_copy_to_stream($resource, $fp = fopen($out, "wb")) > 0; fclose($fp); fclose($resource); return $ret; } /** * Returns all the resources packaged with the plugin * * @return string[] */ public function getResources(){ $resources = []; if(is_dir($this->file . "resources/")){ foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->file . "resources/")) as $resource){ $resources[] = $resource; } } return $resources; } /** * @return Config */ public function getConfig(){ if(!isset($this->config)){ $this->reloadConfig(); } return $this->config; } /** * */ public function saveConfig(){ if($this->getConfig()->save() === false){ $this->getLogger()->critical("Could not save config to " . $this->configFile); } } /** * */ public function saveDefaultConfig(){ if(!file_exists($this->configFile)){ $this->saveResource("config.yml", false); } } /** * */ public function reloadConfig(){ $this->config = new Config($this->configFile); if(($configStream = $this->getResource("config.yml")) !== null){ $this->config->setDefaults(yaml_parse(Config::fixYAMLIndexes(stream_get_contents($configStream)))); fclose($configStream); } } /** * @return Server */ public final function getServer(){ return $this->server; } /** * @return string */ public final function getName(){ return $this->description->getName(); } /** * @return string */ public final function getFullName(){ return $this->description->getFullName(); } /** * @return mixed */ protected function getFile(){ return $this->file; } /** * @return PluginLoader */ public function getPluginLoader(){ return $this->loader; } } loadMap(!is_array($yamlString) ? \yaml_parse($yamlString) : $yamlString); } /** * @param array $plugin * * @throws PluginException */ private function loadMap(array $plugin){ $this->name = preg_replace("[^A-Za-z0-9 _.-]", "", $plugin["name"]); if($this->name === ""){ throw new PluginException("Invalid PluginDescription name"); } $this->name = str_replace(" ", "_", $this->name); $this->version = $plugin["version"]; $this->main = $plugin["main"]; $this->api = !is_array($plugin["api"]) ? [$plugin["api"]] : $plugin["api"]; if(!isset($plugin["geniapi"])){ $this->geniapi = ["1.0.0"]; }else{ $this->geniapi = !is_array($plugin["geniapi"]) ? [$plugin["geniapi"]] : $plugin["geniapi"]; } if(stripos($this->main, "pocketmine\\") === 0){ throw new PluginException("Invalid PluginDescription main, cannot start within the PocketMine namespace"); } if(isset($plugin["commands"]) and is_array($plugin["commands"])){ $this->commands = $plugin["commands"]; } if(isset($plugin["depend"])){ $this->depend = (array) $plugin["depend"]; } if(isset($plugin["softdepend"])){ $this->softDepend = (array) $plugin["softdepend"]; } if(isset($plugin["loadbefore"])){ $this->loadBefore = (array) $plugin["loadbefore"]; } if(isset($plugin["website"])){ $this->website = $plugin["website"]; } if(isset($plugin["description"])){ $this->description = $plugin["description"]; } if(isset($plugin["prefix"])){ $this->prefix = $plugin["prefix"]; } if(isset($plugin["load"])){ $order = strtoupper($plugin["load"]); if(!defined(PluginLoadOrder::class . "::" . $order)){ throw new PluginException("Invalid PluginDescription load"); }else{ $this->order = constant(PluginLoadOrder::class . "::" . $order); } } $this->authors = []; if(isset($plugin["author"])){ $this->authors[] = $plugin["author"]; } if(isset($plugin["authors"])){ foreach($plugin["authors"] as $author){ $this->authors[] = $author; } } if(isset($plugin["permissions"])){ $this->permissions = Permission::loadPermissions($plugin["permissions"]); } } /** * @return string */ public function getFullName(){ return $this->name . " v" . $this->version; } /** * @return array */ public function getCompatibleApis(){ return $this->api; } /** * @return array */ public function getCompatibleGeniApis(){ return $this->geniapi; } /** * @return array */ public function getAuthors(){ return $this->authors; } /** * @return string */ public function getPrefix(){ return $this->prefix; } /** * @return array */ public function getCommands(){ return $this->commands; } /** * @return array */ public function getDepend(){ return $this->depend; } /** * @return string */ public function getDescription(){ return $this->description; } /** * @return array */ public function getLoadBefore(){ return $this->loadBefore; } /** * @return string */ public function getMain(){ return $this->main; } /** * @return string */ public function getName() : string{ return $this->name; } /** * @return int */ public function getOrder(){ return $this->order; } /** * @return Permission[] */ public function getPermissions(){ return $this->permissions; } /** * @return array */ public function getSoftDepend(){ return $this->softDepend; } /** * @return string */ public function getVersion(){ return $this->version; } /** * @return string */ public function getWebsite(){ return $this->website; } }attachments[spl_object_hash($attachment)] = $attachment; } /** * @param \LoggerAttachment $attachment */ public function removeAttachment(\LoggerAttachment $attachment){ unset($this->attachments[spl_object_hash($attachment)]); } public function removeAttachments(){ $this->attachments = []; } /** * @return \LoggerAttachment[] */ public function getAttachments(){ return $this->attachments; } /** * @param Plugin $context */ public function __construct(Plugin $context){ $prefix = $context->getDescription()->getPrefix(); $this->pluginName = $prefix != null ? "[$prefix] " : "[" . $context->getDescription()->getName() . "] "; } /** * @param string $message */ public function emergency($message){ $this->log(LogLevel::EMERGENCY, $message); } /** * @param string $message */ public function alert($message){ $this->log(LogLevel::ALERT, $message); } /** * @param string $message */ public function critical($message){ $this->log(LogLevel::CRITICAL, $message); } /** * @param string $message */ public function error($message){ $this->log(LogLevel::ERROR, $message); } /** * @param string $message */ public function warning($message){ $this->log(LogLevel::WARNING, $message); } /** * @param string $message */ public function notice($message){ $this->log(LogLevel::NOTICE, $message); } /** * @param string $message */ public function info($message){ $this->log(LogLevel::INFO, $message); } /** * @param string $message */ public function debug($message){ $this->log(LogLevel::DEBUG, $message); } /** * @param \Throwable $e * @param null $trace */ public function logException(\Throwable $e, $trace = null){ Server::getInstance()->getLogger()->logException($e, $trace); } /** * @param mixed $level * @param string $message */ public function log($level, $message){ Server::getInstance()->getLogger()->log($level, $this->pluginName . $message); foreach($this->attachments as $attachment){ $attachment->log($level, $message); } } } server = $server; $this->commandMap = $commandMap; } /** * @param string $name * * @return null|Plugin */ public function getPlugin($name){ if(isset($this->plugins[$name])){ return $this->plugins[$name]; } return null; } /** * @param string $loaderName A PluginLoader class name * * @return bool */ public function registerInterface($loaderName){ if(is_subclass_of($loaderName, PluginLoader::class)){ $loader = new $loaderName($this->server); }else{ return false; } $this->fileAssociations[$loaderName] = $loader; return true; } /** * @return Plugin[] */ public function getPlugins(){ return $this->plugins; } /** * @param string $path * @param PluginLoader[] $loaders * * @return Plugin */ public function loadPlugin($path, $loaders = null){ foreach(($loaders === null ? $this->fileAssociations : $loaders) as $loader){ if(preg_match($loader->getPluginFilters(), basename($path)) > 0){ $description = $loader->getPluginDescription($path); if($description instanceof PluginDescription){ if(($plugin = $loader->loadPlugin($path)) instanceof Plugin){ $this->plugins[$plugin->getDescription()->getName()] = $plugin; $pluginCommands = $this->parseYamlCommands($plugin); if(count($pluginCommands) > 0){ $this->commandMap->registerAll($plugin->getDescription()->getName(), $pluginCommands); } return $plugin; } } } } return null; } /** * @param string $directory * @param array $newLoaders * * @return Plugin[] */ public function loadPlugins($directory, $newLoaders = null){ if(is_dir($directory)){ $plugins = []; $loadedPlugins = []; $dependencies = []; $softDependencies = []; if(is_array($newLoaders)){ $loaders = []; foreach($newLoaders as $key){ if(isset($this->fileAssociations[$key])){ $loaders[$key] = $this->fileAssociations[$key]; } } }else{ $loaders = $this->fileAssociations; } foreach($loaders as $loader){ foreach(new \RegexIterator(new \DirectoryIterator($directory), $loader->getPluginFilters()) as $file){ if($file === "." or $file === ".."){ continue; } $file = $directory . $file; try{ $description = $loader->getPluginDescription($file); if($description instanceof PluginDescription){ $name = $description->getName(); if(stripos($name, "pocketmine") !== false or stripos($name, "minecraft") !== false or stripos($name, "mojang") !== false){ $this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.restrictedName"])); continue; }elseif(strpos($name, " ") !== false){ $this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.spacesDiscouraged", [$name])); } if(isset($plugins[$name]) or $this->getPlugin($name) instanceof Plugin){ $this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.duplicateError", [$name])); continue; } $compatible = false; //Check multiple dependencies foreach($description->getCompatibleApis() as $version){ //Format: majorVersion.minorVersion.patch (3.0.0) // or: majorVersion.minorVersion.patch-devBuild (3.0.0-alpha1) if($version !== $this->server->getApiVersion()){ $pluginApi = array_pad(explode("-", $version), 2, ""); //0 = version, 1 = suffix (optional) $serverApi = array_pad(explode("-", $this->server->getApiVersion()), 2, ""); if(strtoupper($pluginApi[1]) !== strtoupper($serverApi[1])){ //Different release phase (alpha vs. beta) or phase build (alpha.1 vs alpha.2) continue; } $pluginNumbers = array_map("intval", explode(".", $pluginApi[0])); $serverNumbers = array_map("intval", explode(".", $serverApi[0])); if($pluginNumbers[0] !== $serverNumbers[0]){ //Completely different API version continue; } if($pluginNumbers[1] > $serverNumbers[1]){ //If the plugin requires new API features, being backwards compatible continue; } } $compatible = true; break; } $compatiblegeniapi = false; foreach($description->getCompatibleGeniApis() as $version){ //Format: majorVersion.minorVersion.patch $version = array_map("intval", explode(".", $version)); $apiVersion = array_map("intval", explode(".", $this->server->getGeniApiVersion())); //Completely different API version if($version[0] > $apiVersion[0]){ continue; } //If the plugin uses new API if($version[0] < $apiVersion[0]){ $compatiblegeniapi = true; break; } //If the plugin requires new API features, being backwards compatible if($version[1] > $apiVersion[1]){ continue; } if($version[1] == $apiVersion[1] and $version[2] > $apiVersion[2]){ continue; } $compatiblegeniapi = true; break; } if($compatible === false){ if($this->server->loadIncompatibleAPI === true){ $this->server->getLogger()->debug("插件{$name}的API与服务器不符,但GenisysPro仍然加载了它"); }else{ $this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.incompatibleAPI"])); continue; } } if($compatiblegeniapi === false){ $this->server->getLogger()->error("Could not load plugin '{$description->getName()}': Incompatible GeniAPI version"); continue; } $plugins[$name] = $file; $softDependencies[$name] = (array) $description->getSoftDepend(); $dependencies[$name] = (array) $description->getDepend(); foreach($description->getLoadBefore() as $before){ if(isset($softDependencies[$before])){ $softDependencies[$before][] = $name; }else{ $softDependencies[$before] = [$name]; } } } }catch(\Throwable $e){ $this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.plugin.fileError", [$file, $directory, $e->getMessage()])); $this->server->getLogger()->logException($e); } } } while(count($plugins) > 0){ $missingDependency = true; foreach($plugins as $name => $file){ if(isset($dependencies[$name])){ foreach($dependencies[$name] as $key => $dependency){ if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){ unset($dependencies[$name][$key]); }elseif(!isset($plugins[$dependency])){ $this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.unknownDependency"])); break; } } if(count($dependencies[$name]) === 0){ unset($dependencies[$name]); } } if(isset($softDependencies[$name])){ foreach($softDependencies[$name] as $key => $dependency){ if(isset($loadedPlugins[$dependency]) or $this->getPlugin($dependency) instanceof Plugin){ unset($softDependencies[$name][$key]); } } if(count($softDependencies[$name]) === 0){ unset($softDependencies[$name]); } } if(!isset($dependencies[$name]) and !isset($softDependencies[$name])){ unset($plugins[$name]); $missingDependency = false; if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){ $loadedPlugins[$name] = $plugin; }else{ $this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name])); } } } if($missingDependency === true){ foreach($plugins as $name => $file){ if(!isset($dependencies[$name])){ unset($softDependencies[$name]); unset($plugins[$name]); $missingDependency = false; if($plugin = $this->loadPlugin($file, $loaders) and $plugin instanceof Plugin){ $loadedPlugins[$name] = $plugin; }else{ $this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.genericLoadError", [$name])); } } } //No plugins loaded :( if($missingDependency === true){ foreach($plugins as $name => $file){ $this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.loadError", [$name, "%pocketmine.plugin.circularDependency"])); } $plugins = []; } } } TimingsCommand::$timingStart = microtime(true); return $loadedPlugins; }else{ TimingsCommand::$timingStart = microtime(true); return []; } } /** * @param string $name * * @return null|Permission */ public function getPermission($name){ if(isset($this->permissions[$name])){ return $this->permissions[$name]; } return null; } /** * @param Permission $permission * * @return bool */ public function addPermission(Permission $permission){ if(!isset($this->permissions[$permission->getName()])){ $this->permissions[$permission->getName()] = $permission; $this->calculatePermissionDefault($permission); return true; } return false; } /** * @param string|Permission $permission */ public function removePermission($permission){ if($permission instanceof Permission){ unset($this->permissions[$permission->getName()]); }else{ unset($this->permissions[$permission]); } } /** * @param bool $op * * @return Permission[] */ public function getDefaultPermissions($op){ if($op === true){ return $this->defaultPermsOp; }else{ return $this->defaultPerms; } } /** * @param Permission $permission */ public function recalculatePermissionDefaults(Permission $permission){ if(isset($this->permissions[$permission->getName()])){ unset($this->defaultPermsOp[$permission->getName()]); unset($this->defaultPerms[$permission->getName()]); $this->calculatePermissionDefault($permission); } } /** * @param Permission $permission */ private function calculatePermissionDefault(Permission $permission){ Timings::$permissionDefaultTimer->startTiming(); if($permission->getDefault() === Permission::DEFAULT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){ $this->defaultPermsOp[$permission->getName()] = $permission; $this->dirtyPermissibles(true); } if($permission->getDefault() === Permission::DEFAULT_NOT_OP or $permission->getDefault() === Permission::DEFAULT_TRUE){ $this->defaultPerms[$permission->getName()] = $permission; $this->dirtyPermissibles(false); } Timings::$permissionDefaultTimer->stopTiming(); } /** * @param bool $op */ private function dirtyPermissibles($op){ foreach($this->getDefaultPermSubscriptions($op) as $p){ $p->recalculatePermissions(); } } /** * @param string $permission * @param Permissible $permissible */ public function subscribeToPermission($permission, Permissible $permissible){ if(!isset($this->permSubs[$permission])){ $this->permSubs[$permission] = []; } $this->permSubs[$permission][spl_object_hash($permissible)] = $permissible; } /** * @param string $permission * @param Permissible $permissible */ public function unsubscribeFromPermission($permission, Permissible $permissible){ if(isset($this->permSubs[$permission])){ unset($this->permSubs[$permission][spl_object_hash($permissible)]); if(count($this->permSubs[$permission]) === 0){ unset($this->permSubs[$permission]); } } } /** * @param string $permission * * @return Permissible[] */ public function getPermissionSubscriptions($permission){ if(isset($this->permSubs[$permission])){ return $this->permSubs[$permission]; $subs = []; foreach($this->permSubs[$permission] as $k => $perm){ /** @var \WeakRef $perm */ if($perm->acquire()){ $subs[] = $perm->get(); $perm->release(); }else{ unset($this->permSubs[$permission][$k]); } } return $subs; } return []; } /** * @param bool $op * @param Permissible $permissible */ public function subscribeToDefaultPerms($op, Permissible $permissible){ if($op === true){ $this->defSubsOp[spl_object_hash($permissible)] = $permissible; }else{ $this->defSubs[spl_object_hash($permissible)] = $permissible; } } /** * @param bool $op * @param Permissible $permissible */ public function unsubscribeFromDefaultPerms($op, Permissible $permissible){ if($op === true){ unset($this->defSubsOp[spl_object_hash($permissible)]); }else{ unset($this->defSubs[spl_object_hash($permissible)]); } } /** * @param bool $op * * @return Permissible[] */ public function getDefaultPermSubscriptions($op){ if($op === true){ return $this->defSubsOp; foreach($this->defSubsOp as $k => $perm){ /** @var \WeakRef $perm */ if($perm->acquire()){ $subs[] = $perm->get(); $perm->release(); }else{ unset($this->defSubsOp[$k]); } } }else{ return $this->defSubs; foreach($this->defSubs as $k => $perm){ /** @var \WeakRef $perm */ if($perm->acquire()){ $subs[] = $perm->get(); $perm->release(); }else{ unset($this->defSubs[$k]); } } } return $subs; } /** * @return Permission[] */ public function getPermissions(){ return $this->permissions; } /** * @param Plugin $plugin * * @return bool */ public function isPluginEnabled(Plugin $plugin){ if($plugin instanceof Plugin and isset($this->plugins[$plugin->getDescription()->getName()])){ return $plugin->isEnabled(); }else{ return false; } } /** * @param Plugin $plugin */ public function enablePlugin(Plugin $plugin){ if(!$plugin->isEnabled()){ try{ foreach($plugin->getDescription()->getPermissions() as $perm){ $this->addPermission($perm); } $plugin->getPluginLoader()->enablePlugin($plugin); }catch(\Throwable $e){ $this->server->getLogger()->logException($e); $this->disablePlugin($plugin); } } } /** * @param Plugin $plugin * * @return PluginCommand[] */ protected function parseYamlCommands(Plugin $plugin){ $pluginCmds = []; foreach($plugin->getDescription()->getCommands() as $key => $data){ if(strpos($key, ":") !== false){ $this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.commandError", [$key, $plugin->getDescription()->getFullName()])); continue; } if(is_array($data)){ $newCmd = new PluginCommand($key, $plugin); if(isset($data["description"])){ $newCmd->setDescription($data["description"]); } if(isset($data["usage"])){ $newCmd->setUsage($data["usage"]); } if(isset($data["aliases"]) and is_array($data["aliases"])){ $aliasList = []; foreach($data["aliases"] as $alias){ if(strpos($alias, ":") !== false){ $this->server->getLogger()->critical($this->server->getLanguage()->translateString("pocketmine.plugin.aliasError", [$alias, $plugin->getDescription()->getFullName()])); continue; } $aliasList[] = $alias; } $newCmd->setAliases($aliasList); } if(isset($data["permission"])){ $newCmd->setPermission($data["permission"]); } if(isset($data["permission-message"])){ $newCmd->setPermissionMessage($data["permission-message"]); } $pluginCmds[] = $newCmd; } } return $pluginCmds; } public function disablePlugins(){ foreach($this->getPlugins() as $plugin){ $this->disablePlugin($plugin); } } /** * @param Plugin $plugin */ public function disablePlugin(Plugin $plugin){ if($plugin->isEnabled()){ try{ $plugin->getPluginLoader()->disablePlugin($plugin); }catch(\Throwable $e){ $this->server->getLogger()->logException($e); } $this->server->getScheduler()->cancelTasks($plugin); HandlerList::unregisterAll($plugin); foreach($plugin->getDescription()->getPermissions() as $perm){ $this->removePermission($perm); } } } public function clearPlugins(){ $this->disablePlugins(); $this->plugins = []; $this->fileAssociations = []; $this->permissions = []; $this->defaultPerms = []; $this->defaultPermsOp = []; } /** * Calls an event * * @param Event $event */ public function callEvent(Event $event){ foreach($event->getHandlers()->getRegisteredListeners() as $registration){ if(!$registration->getPlugin()->isEnabled()){ continue; } try{ $registration->callEvent($event); }catch(\Throwable $e){ $this->server->getLogger()->critical( $this->server->getLanguage()->translateString("pocketmine.plugin.eventError", [ $event->getEventName(), $registration->getPlugin()->getDescription()->getFullName(), $e->getMessage(), get_class($registration->getListener()) ])); $this->server->getLogger()->logException($e); } } } /** * Registers all the events in the given Listener class * * @param Listener $listener * @param Plugin $plugin * * @throws PluginException */ public function registerEvents(Listener $listener, Plugin $plugin){ if(!$plugin->isEnabled()){ throw new PluginException("Plugin attempted to register " . get_class($listener) . " while not enabled"); } $reflection = new \ReflectionClass(get_class($listener)); foreach($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method){ if(!$method->isStatic()){ $priority = EventPriority::NORMAL; $ignoreCancelled = false; if(preg_match("/^[\t ]*\\* @priority[\t ]{1,}([a-zA-Z]{1,})/m", (string) $method->getDocComment(), $matches) > 0){ $matches[1] = strtoupper($matches[1]); if(defined(EventPriority::class . "::" . $matches[1])){ $priority = constant(EventPriority::class . "::" . $matches[1]); } } if(preg_match("/^[\t ]*\\* @ignoreCancelled[\t ]{1,}([a-zA-Z]{1,})/m", (string) $method->getDocComment(), $matches) > 0){ $matches[1] = strtolower($matches[1]); if($matches[1] === "false"){ $ignoreCancelled = false; }elseif($matches[1] === "true"){ $ignoreCancelled = true; } } $parameters = $method->getParameters(); if(count($parameters) === 1 and $parameters[0]->getClass() instanceof \ReflectionClass and is_subclass_of($parameters[0]->getClass()->getName(), Event::class)){ $class = $parameters[0]->getClass()->getName(); $reflection = new \ReflectionClass($class); if(strpos((string) $reflection->getDocComment(), "@deprecated") !== false and $this->server->getProperty("settings.deprecated-verbose", true)){ $this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.plugin.deprecatedEvent", [ $plugin->getName(), $class, get_class($listener) . "->" . $method->getName() . "()" ])); } $this->registerEvent($class, $listener, $priority, new MethodEventExecutor($method->getName()), $plugin, $ignoreCancelled); } } } } /** * @param string $event Class name that extends Event * @param Listener $listener * @param int $priority * @param EventExecutor $executor * @param Plugin $plugin * @param bool $ignoreCancelled * * @throws PluginException */ public function registerEvent($event, Listener $listener, $priority, EventExecutor $executor, Plugin $plugin, $ignoreCancelled = false){ if(!is_subclass_of($event, Event::class)){ throw new PluginException($event . " is not an Event"); } $class = new \ReflectionClass($event); if($class->isAbstract()){ throw new PluginException($event . " is an abstract Event"); } if($class->getProperty("handlerList")->getDeclaringClass()->getName() !== $event){ throw new PluginException($event . " does not have a handler list"); } if(!$plugin->isEnabled()){ throw new PluginException("Plugin attempted to register " . $event . " while not enabled"); } $timings = new TimingsHandler("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . get_class($listener) . "::" . ($executor instanceof MethodEventExecutor ? $executor->getMethod() : "???") . "(" . (new \ReflectionClass($event))->getShortName() . ")", self::$pluginParentTimer); $this->getEventListeners($event)->register(new RegisteredListener($listener, $executor, $priority, $plugin, $ignoreCancelled, $timings)); } /** * @param $event * * @return HandlerList */ private function getEventListeners($event){ if($event::$handlerList === null){ $event::$handlerList = new HandlerList(); } return $event::$handlerList; } /** * @return bool */ public function useTimings(){ return self::$useTimings; } /** * @param bool $use */ public function setUseTimings($use){ self::$useTimings = (bool) $use; } } listener = $listener; $this->priority = $priority; $this->plugin = $plugin; $this->executor = $executor; $this->ignoreCancelled = $ignoreCancelled; $this->timings = $timings; } /** * @return Listener */ public function getListener(){ return $this->listener; } /** * @return Plugin */ public function getPlugin(){ return $this->plugin; } /** * @return int */ public function getPriority(){ return $this->priority; } /** * @param Event $event */ public function callEvent(Event $event){ if($event instanceof Cancellable and $event->isCancelled() and $this->isIgnoringCancelled()){ return; } $this->timings->startTiming(); $this->executor->execute($this->listener, $event); $this->timings->stopTiming(); } public function __destruct(){ $this->timings->remove(); } /** * @return bool */ public function isIgnoringCancelled(){ return $this->ignoreCancelled === true; } }server = $server; } /** * Loads the plugin contained in $file * * @param string $file * * @return Plugin * * @throws \Throwable */ public function loadPlugin($file){ if(($description = $this->getPluginDescription($file)) instanceof PluginDescription){ $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.load", [$description->getFullName()])); $dataFolder = dirname($file) . DIRECTORY_SEPARATOR . $description->getName(); if(file_exists($dataFolder) and !is_dir($dataFolder)){ throw new \InvalidStateException("Projected dataFolder '" . $dataFolder . "' for " . $description->getName() . " exists and is not a directory"); } include_once($file); $className = $description->getMain(); if(class_exists($className, true)){ $plugin = new $className(); $this->initPlugin($plugin, $description, $dataFolder, $file); return $plugin; }else{ throw new PluginException("Couldn't load plugin " . $description->getName() . ": main class not found"); } } return null; } /** * Gets the PluginDescription from the file * * @param string $file * * @return PluginDescription */ public function getPluginDescription($file){ $content = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); $data = []; $insideHeader = false; foreach($content as $line){ if(!$insideHeader and strpos($line, "/**") !== false){ $insideHeader = true; } if(preg_match("/^[ \t]+\\*[ \t]+@([a-zA-Z]+)([ \t]+(.*))?$/", $line, $matches) > 0){ $key = $matches[1]; $content = trim($matches[3] ?? ""); if($key === "notscript"){ return null; } $data[$key] = $content; } if($insideHeader and strpos($line, "**/") !== false){ break; } } if($insideHeader){ return new PluginDescription($data); } return null; } /** * Returns the filename patterns that this loader accepts * * @return string */ public function getPluginFilters(){ return "/\\.php$/i"; } /** * @param PluginBase $plugin * @param PluginDescription $description * @param string $dataFolder * @param string $file */ private function initPlugin(PluginBase $plugin, PluginDescription $description, $dataFolder, $file){ $plugin->init($this, $this->server, $description, $dataFolder, $file); $plugin->onLoad(); } /** * @param Plugin $plugin */ public function enablePlugin(Plugin $plugin){ if($plugin instanceof PluginBase and !$plugin->isEnabled()){ $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.enable", [$plugin->getDescription()->getFullName()])); $plugin->setEnabled(true); $this->server->getPluginManager()->callEvent(new PluginEnableEvent($plugin)); } } /** * @param Plugin $plugin */ public function disablePlugin(Plugin $plugin){ if($plugin instanceof PluginBase and $plugin->isEnabled()){ $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.plugin.disable", [$plugin->getDescription()->getFullName()])); $this->server->getPluginManager()->callEvent(new PluginDisableEvent($plugin)); $plugin->setEnabled(false); } } }packId = $packId; $this->version = $version; $this->packSize = $packSize; } /** * @return string */ public function getPackId() : string{ return $this->packId; } /** * @return string */ public function getVersion() : string{ return $this->version; } /** * @return int */ public function getPackSize(){ return $this->packSize; } }server = $server; $this->path = $path; if(!file_exists($this->path)){ $this->server->getLogger()->debug($this->server->getLanguage()->translateString("pocketmine.resourcepacks.createFolder", [$path])); mkdir($this->path); }elseif(!is_dir($this->path)){ throw new \InvalidArgumentException($this->server->getLanguage()->translateString("pocketmine.resourcepacks.notFolder", [$path])); } if(!file_exists($this->path . "resource_packs.yml")){ $lang = $this->server->getProperty("settings.language"); if(file_exists($this->server->getFilePath() . "src/pocketmine/resources/resource_packs_$lang.yml")){ $content = file_get_contents($file = $this->server->getFilePath() . "src/pocketmine/resources/resource_packs_$lang.yml"); }else{ $content = file_get_contents($file = $this->server->getFilePath() . "src/pocketmine/resources/resource_packs_eng.yml"); } file_put_contents($this->path . "resource_packs.yml", $content); } $this->resourcePacksConfig = new Config($this->path . "resource_packs.yml", Config::YAML, []); $this->serverForceResources = (bool) $this->resourcePacksConfig->get("force_resources", false); $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.resourcepacks.load")); foreach($this->resourcePacksConfig->get("resource_stack", []) as $pos => $pack){ try{ $packPath = $this->path . DIRECTORY_SEPARATOR . $pack; if(file_exists($packPath)){ $newPack = null; //Detect the type of resource pack. if(is_dir($packPath)){ $this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.resourcepacks.folderNotSupported", [$path])); }else{ $info = new \SplFileInfo($packPath); switch($info->getExtension()){ case "zip": $newPack = new ZippedResourcePack($packPath); break; case "mcpack": $newPack = new ZippedResourcePack($packPath); break; default: $this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.resourcepacks.unsupportedType", [$path])); break; } } if($newPack instanceof ResourcePack){ $this->resourcePacks[] = $newPack; $this->uuidList[$newPack->getPackId()] = $newPack; } }else{ $this->server->getLogger()->warning($this->server->getLanguage()->translateString("pocketmine.resourcepacks.packNotFound", [$path])); } }catch(\Throwable $e){ $this->server->getLogger()->logException($e); } } $this->server->getLogger()->debug($this->server->getLanguage()->translateString("pocketmine.resourcepacks.loadFinished", [count($this->resourcePacks)])); } /** * @return bool */ public function resourcePacksRequired() : bool{ return $this->serverForceResources; } /** * @return ResourcePack[] */ public function getResourceStack() : array{ return $this->resourcePacks; } /** * @param string $id * * @return ResourcePack|null */ public function getPackById(string $id){ return $this->uuidList[$id] ?? null; } /** * @return string[] */ public function getPackIdList() : array{ return array_keys($this->uuidList); } } format_version) or !isset($manifest->header) or !isset($manifest->modules)){ return false; } return isset($manifest->header->description) and isset($manifest->header->name) and isset($manifest->header->uuid) and isset($manifest->header->version) and count($manifest->header->version) === 3; } /** @var string */ protected $path; /** @var \stdClass */ protected $manifest; /** @var string */ protected $sha256 = null; /** @var resource */ protected $fileResource; /** * ZippedResourcePack constructor. * * @param string $zipPath */ public function __construct(string $zipPath){ $this->path = $zipPath; if(!file_exists($zipPath)){ throw new \InvalidArgumentException("无法打开材质包 $zipPath: 文件夹无法打开"); } $archive = new \ZipArchive(); if(($openResult = $archive->open($zipPath)) !== true){ throw new \InvalidStateException("打开 $zipPath $openResult");//Yeah, I don't speak, that... } if(($manifestData = $archive->getFromName("manifest.json")) === false){ if(($manifestData = $archive->getFromName("pack_manifest.json")) === false){ throw new \InvalidStateException("无法加载材质包 $zipPath: 找不到主类"); } } $archive->close(); $manifest = json_decode($manifestData); if(!self::verifyManifest($manifest)){ throw new \InvalidStateException("无法加载材质包 $zipPath: 主类错误或不完整"); } $this->manifest = $manifest; $this->fileResource = fopen($zipPath, "rb"); } /** * @return string */ public function getPackName() : string{ return $this->manifest->header->name; } /** * @return string */ public function getPackVersion() : string{ return implode(".", $this->manifest->header->version); } /** * @return string */ public function getPackId() : string{ return $this->manifest->header->uuid; } /** * @return int */ public function getPackSize() : int{ return filesize($this->path); } /** * @param bool $cached * * @return string */ public function getSha256(bool $cached = true) : string{ if($this->sha256 === null or !$cached){ $this->sha256 = hash_file("sha256", $this->path, true); } return $this->sha256; } /** * @param int $start * @param int $length * * @return string */ public function getPackChunk(int $start, int $length) : string{ fseek($this->fileResource, $start); if(feof($this->fileResource)){ throw new \RuntimeException("Requested a resource pack chunk with invalid start offset"); } return fread($this->fileResource, $length); } }{ "aliases": [], "description": "insert_description_here", "overloads": { "default": { "input": { "parameters": [ { "name": "args", "type": "rawtext", "optional": true } ] }, "output": {} } }, "permission": "any" } [ { "id": 138, "damage": 0, "count": 1, "nbt": "" }, { "id": 66, "damage": 0, "count": 1, "nbt": "" }, { "id": 27, "damage": 0, "count": 1, "nbt": "" }, { "id": 28, "damage": 0, "count": 1, "nbt": "" }, { "id": 126, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": 0, "count": 1, "nbt": "" }, { "id": 98, "damage": 0, "count": 1, "nbt": "" }, { "id": 98, "damage": 1, "count": 1, "nbt": "" }, { "id": 98, "damage": 2, "count": 1, "nbt": "" }, { "id": 98, "damage": 3, "count": 1, "nbt": "" }, { "id": 48, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 45, "damage": 0, "count": 1, "nbt": "" }, { "id": 1, "damage": 0, "count": 1, "nbt": "" }, { "id": 1, "damage": 1, "count": 1, "nbt": "" }, { "id": 1, "damage": 2, "count": 1, "nbt": "" }, { "id": 1, "damage": 3, "count": 1, "nbt": "" }, { "id": 1, "damage": 4, "count": 1, "nbt": "" }, { "id": 1, "damage": 5, "count": 1, "nbt": "" }, { "id": 1, "damage": 6, "count": 1, "nbt": "" }, { "id": 3, "damage": 0, "count": 1, "nbt": "" }, { "id": 243, "damage": 0, "count": 1, "nbt": "" }, { "id": 2, "damage": 0, "count": 1, "nbt": "" }, { "id": 110, "damage": 0, "count": 1, "nbt": "" }, { "id": 82, "damage": 0, "count": 1, "nbt": "" }, { "id": 172, "damage": 0, "count": 1, "nbt": "" }, { "id": 159, "damage": 0, "count": 1, "nbt": "" }, { "id": 159, "damage": 8, "count": 1, "nbt": "" }, { "id": 159, "damage": 7, "count": 1, "nbt": "" }, { "id": 159, "damage": 15, "count": 1, "nbt": "" }, { "id": 159, "damage": 12, "count": 1, "nbt": "" }, { "id": 159, "damage": 14, "count": 1, "nbt": "" }, { "id": 159, "damage": 1, "count": 1, "nbt": "" }, { "id": 159, "damage": 4, "count": 1, "nbt": "" }, { "id": 159, "damage": 5, "count": 1, "nbt": "" }, { "id": 159, "damage": 13, "count": 1, "nbt": "" }, { "id": 159, "damage": 9, "count": 1, "nbt": "" }, { "id": 159, "damage": 3, "count": 1, "nbt": "" }, { "id": 159, "damage": 11, "count": 1, "nbt": "" }, { "id": 159, "damage": 10, "count": 1, "nbt": "" }, { "id": 159, "damage": 2, "count": 1, "nbt": "" }, { "id": 159, "damage": 6, "count": 1, "nbt": "" }, { "id": 24, "damage": 0, "count": 1, "nbt": "" }, { "id": 24, "damage": 1, "count": 1, "nbt": "" }, { "id": 24, "damage": 2, "count": 1, "nbt": "" }, { "id": 179, "damage": 0, "count": 1, "nbt": "" }, { "id": 179, "damage": 1, "count": 1, "nbt": "" }, { "id": 179, "damage": 2, "count": 1, "nbt": "" }, { "id": 12, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": 1, "count": 1, "nbt": "" }, { "id": 13, "damage": 0, "count": 1, "nbt": "" }, { "id": 139, "damage": 0, "count": 1, "nbt": "" }, { "id": 139, "damage": 1, "count": 1, "nbt": "" }, { "id": 17, "damage": 0, "count": 1, "nbt": "" }, { "id": 17, "damage": 1, "count": 1, "nbt": "" }, { "id": 17, "damage": 2, "count": 1, "nbt": "" }, { "id": 17, "damage": 3, "count": 1, "nbt": "" }, { "id": 162, "damage": 0, "count": 1, "nbt": "" }, { "id": 162, "damage": 1, "count": 1, "nbt": "" }, { "id": 112, "damage": 0, "count": 1, "nbt": "" }, { "id": 87, "damage": 0, "count": 1, "nbt": "" }, { "id": 88, "damage": 0, "count": 1, "nbt": "" }, { "id": 7, "damage": 0, "count": 1, "nbt": "" }, { "id": 67, "damage": 0, "count": 1, "nbt": "" }, { "id": 53, "damage": 0, "count": 1, "nbt": "" }, { "id": 134, "damage": 0, "count": 1, "nbt": "" }, { "id": 135, "damage": 0, "count": 1, "nbt": "" }, { "id": 136, "damage": 0, "count": 1, "nbt": "" }, { "id": 163, "damage": 0, "count": 1, "nbt": "" }, { "id": 164, "damage": 0, "count": 1, "nbt": "" }, { "id": 108, "damage": 0, "count": 1, "nbt": "" }, { "id": 128, "damage": 0, "count": 1, "nbt": "" }, { "id": 180, "damage": 0, "count": 1, "nbt": "" }, { "id": 109, "damage": 0, "count": 1, "nbt": "" }, { "id": 114, "damage": 0, "count": 1, "nbt": "" }, { "id": 156, "damage": 0, "count": 1, "nbt": "" }, { "id": 203, "damage": 0, "count": 1, "nbt": "" }, { "id": 44, "damage": 0, "count": 1, "nbt": "" }, { "id": 44, "damage": 3, "count": 1, "nbt": "" }, { "id": 111, "damage": 0, "count": 1, "nbt": "" }, { "id": 158, "damage": 0, "count": 1, "nbt": "" }, { "id": 158, "damage": 1, "count": 1, "nbt": "" }, { "id": 158, "damage": 2, "count": 1, "nbt": "" }, { "id": 158, "damage": 3, "count": 1, "nbt": "" }, { "id": 158, "damage": 4, "count": 1, "nbt": "" }, { "id": 158, "damage": 5, "count": 1, "nbt": "" }, { "id": 44, "damage": 4, "count": 1, "nbt": "" }, { "id": 44, "damage": 1, "count": 1, "nbt": "" }, { "id": 182, "damage": 0, "count": 1, "nbt": "" }, { "id": 44, "damage": 5, "count": 1, "nbt": "" }, { "id": 44, "damage": 7, "count": 1, "nbt": "" }, { "id": 44, "damage": 6, "count": 1, "nbt": "" }, { "id": 182, "damage": 1, "count": 1, "nbt": "" }, { "id": 155, "damage": 0, "count": 1, "nbt": "" }, { "id": 155, "damage": 2, "count": 1, "nbt": "" }, { "id": 155, "damage": 1, "count": 1, "nbt": "" }, { "id": 168, "damage": 0, "count": 1, "nbt": "" }, { "id": 168, "damage": 2, "count": 1, "nbt": "" }, { "id": 168, "damage": 1, "count": 1, "nbt": "" }, { "id": 169, "damage": 0, "count": 1, "nbt": "" }, { "id": 201, "damage": 0, "count": 1, "nbt": "" }, { "id": 201, "damage": 2, "count": 1, "nbt": "" }, { "id": 240, "damage": 0, "count": 1, "nbt": "" }, { "id": 200, "damage": 0, "count": 1, "nbt": "" }, { "id": 263, "damage": 0, "count": 1, "nbt": "" }, { "id": 263, "damage": 1, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 388, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 281, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 288, "damage": 0, "count": 1, "nbt": "" }, { "id": 318, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 415, "damage": 0, "count": 1, "nbt": "" }, { "id": 337, "damage": 0, "count": 1, "nbt": "" }, { "id": 353, "damage": 0, "count": 1, "nbt": "" }, { "id": 336, "damage": 0, "count": 1, "nbt": "" }, { "id": 405, "damage": 0, "count": 1, "nbt": "" }, { "id": 16, "damage": 0, "count": 1, "nbt": "" }, { "id": 15, "damage": 0, "count": 1, "nbt": "" }, { "id": 14, "damage": 0, "count": 1, "nbt": "" }, { "id": 56, "damage": 0, "count": 1, "nbt": "" }, { "id": 21, "damage": 0, "count": 1, "nbt": "" }, { "id": 73, "damage": 0, "count": 1, "nbt": "" }, { "id": 129, "damage": 0, "count": 1, "nbt": "" }, { "id": 153, "damage": 0, "count": 1, "nbt": "" }, { "id": 41, "damage": 0, "count": 1, "nbt": "" }, { "id": 42, "damage": 0, "count": 1, "nbt": "" }, { "id": 57, "damage": 0, "count": 1, "nbt": "" }, { "id": 22, "damage": 0, "count": 1, "nbt": "" }, { "id": 173, "damage": 0, "count": 1, "nbt": "" }, { "id": 133, "damage": 0, "count": 1, "nbt": "" }, { "id": 152, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 49, "damage": 0, "count": 1, "nbt": "" }, { "id": 79, "damage": 0, "count": 1, "nbt": "" }, { "id": 174, "damage": 0, "count": 1, "nbt": "" }, { "id": 80, "damage": 0, "count": 1, "nbt": "" }, { "id": 78, "damage": 0, "count": 1, "nbt": "" }, { "id": 20, "damage": 0, "count": 1, "nbt": "" }, { "id": 89, "damage": 0, "count": 1, "nbt": "" }, { "id": 106, "damage": 0, "count": 1, "nbt": "" }, { "id": 65, "damage": 0, "count": 1, "nbt": "" }, { "id": 19, "damage": 0, "count": 1, "nbt": "" }, { "id": 19, "damage": 1, "count": 1, "nbt": "" }, { "id": 50, "damage": 0, "count": 1, "nbt": "" }, { "id": 102, "damage": 0, "count": 1, "nbt": "" }, { "id": 325, "damage": 0, "count": 1, "nbt": "" }, { "id": 325, "damage": 1, "count": 1, "nbt": "" }, { "id": 325, "damage": 8, "count": 1, "nbt": "" }, { "id": 325, "damage": 10, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 340, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 352, "damage": 0, "count": 1, "nbt": "" }, { "id": 324, "damage": 0, "count": 1, "nbt": "" }, { "id": 427, "damage": 0, "count": 1, "nbt": "" }, { "id": 428, "damage": 0, "count": 1, "nbt": "" }, { "id": 429, "damage": 0, "count": 1, "nbt": "" }, { "id": 430, "damage": 0, "count": 1, "nbt": "" }, { "id": 431, "damage": 0, "count": 1, "nbt": "" }, { "id": 330, "damage": 0, "count": 1, "nbt": "" }, { "id": 395, "damage": 0, "count": 1, "nbt": "" }, { "id": 395, "damage": 2, "count": 1, "nbt": "" }, { "id": 96, "damage": 0, "count": 1, "nbt": "" }, { "id": 167, "damage": 0, "count": 1, "nbt": "" }, { "id": 85, "damage": 0, "count": 1, "nbt": "" }, { "id": 85, "damage": 1, "count": 1, "nbt": "" }, { "id": 85, "damage": 2, "count": 1, "nbt": "" }, { "id": 85, "damage": 3, "count": 1, "nbt": "" }, { "id": 85, "damage": 4, "count": 1, "nbt": "" }, { "id": 85, "damage": 5, "count": 1, "nbt": "" }, { "id": 113, "damage": 0, "count": 1, "nbt": "" }, { "id": 107, "damage": 0, "count": 1, "nbt": "" }, { "id": 183, "damage": 0, "count": 1, "nbt": "" }, { "id": 184, "damage": 0, "count": 1, "nbt": "" }, { "id": 185, "damage": 0, "count": 1, "nbt": "" }, { "id": 187, "damage": 0, "count": 1, "nbt": "" }, { "id": 186, "damage": 0, "count": 1, "nbt": "" }, { "id": 101, "damage": 0, "count": 1, "nbt": "" }, { "id": 355, "damage": 0, "count": 1, "nbt": "" }, { "id": 355, "damage": 8, "count": 1, "nbt": "" }, { "id": 355, "damage": 7, "count": 1, "nbt": "" }, { "id": 355, "damage": 15, "count": 1, "nbt": "" }, { "id": 355, "damage": 12, "count": 1, "nbt": "" }, { "id": 355, "damage": 14, "count": 1, "nbt": "" }, { "id": 355, "damage": 1, "count": 1, "nbt": "" }, { "id": 355, "damage": 4, "count": 1, "nbt": "" }, { "id": 355, "damage": 5, "count": 1, "nbt": "" }, { "id": 355, "damage": 13, "count": 1, "nbt": "" }, { "id": 355, "damage": 9, "count": 1, "nbt": "" }, { "id": 355, "damage": 3, "count": 1, "nbt": "" }, { "id": 355, "damage": 11, "count": 1, "nbt": "" }, { "id": 355, "damage": 10, "count": 1, "nbt": "" }, { "id": 355, "damage": 2, "count": 1, "nbt": "" }, { "id": 355, "damage": 6, "count": 1, "nbt": "" }, { "id": 47, "damage": 0, "count": 1, "nbt": "" }, { "id": 323, "damage": 0, "count": 1, "nbt": "" }, { "id": 321, "damage": 0, "count": 1, "nbt": "" }, { "id": 389, "damage": 0, "count": 1, "nbt": "" }, { "id": 58, "damage": 0, "count": 1, "nbt": "" }, { "id": 245, "damage": 0, "count": 1, "nbt": "" }, { "id": 54, "damage": 0, "count": 1, "nbt": "" }, { "id": 146, "damage": 0, "count": 1, "nbt": "" }, { "id": 61, "damage": 0, "count": 1, "nbt": "" }, { "id": 379, "damage": 0, "count": 1, "nbt": "" }, { "id": 380, "damage": 0, "count": 1, "nbt": "" }, { "id": 25, "damage": 0, "count": 1, "nbt": "" }, { "id": 46, "damage": 0, "count": 1, "nbt": "" }, { "id": 206, "damage": 0, "count": 1, "nbt": "" }, { "id": 121, "damage": 0, "count": 1, "nbt": "" }, { "id": 208, "damage": 0, "count": 1, "nbt": "" }, { "id": 120, "damage": 0, "count": 1, "nbt": "" }, { "id": 218, "damage": 0, "count": 1, "nbt": "" }, { "id": 218, "damage": 8, "count": 1, "nbt": "" }, { "id": 218, "damage": 7, "count": 1, "nbt": "" }, { "id": 218, "damage": 15, "count": 1, "nbt": "" }, { "id": 218, "damage": 12, "count": 1, "nbt": "" }, { "id": 218, "damage": 14, "count": 1, "nbt": "" }, { "id": 218, "damage": 1, "count": 1, "nbt": "" }, { "id": 218, "damage": 4, "count": 1, "nbt": "" }, { "id": 218, "damage": 5, "count": 1, "nbt": "" }, { "id": 218, "damage": 13, "count": 1, "nbt": "" }, { "id": 218, "damage": 9, "count": 1, "nbt": "" }, { "id": 218, "damage": 3, "count": 1, "nbt": "" }, { "id": 218, "damage": 11, "count": 1, "nbt": "" }, { "id": 218, "damage": 10, "count": 1, "nbt": "" }, { "id": 218, "damage": 2, "count": 1, "nbt": "" }, { "id": 218, "damage": 6, "count": 1, "nbt": "" }, { "id": 145, "damage": 0, "count": 1, "nbt": "" }, { "id": 145, "damage": 1, "count": 1, "nbt": "" }, { "id": 145, "damage": 2, "count": 1, "nbt": "" }, { "id": 37, "damage": 0, "count": 1, "nbt": "" }, { "id": 38, "damage": 0, "count": 1, "nbt": "" }, { "id": 38, "damage": 1, "count": 1, "nbt": "" }, { "id": 38, "damage": 2, "count": 1, "nbt": "" }, { "id": 38, "damage": 3, "count": 1, "nbt": "" }, { "id": 38, "damage": 4, "count": 1, "nbt": "" }, { "id": 38, "damage": 5, "count": 1, "nbt": "" }, { "id": 38, "damage": 6, "count": 1, "nbt": "" }, { "id": 38, "damage": 7, "count": 1, "nbt": "" }, { "id": 38, "damage": 8, "count": 1, "nbt": "" }, { "id": 175, "damage": 0, "count": 1, "nbt": "" }, { "id": 175, "damage": 1, "count": 1, "nbt": "" }, { "id": 175, "damage": 2, "count": 1, "nbt": "" }, { "id": 175, "damage": 3, "count": 1, "nbt": "" }, { "id": 175, "damage": 4, "count": 1, "nbt": "" }, { "id": 175, "damage": 5, "count": 1, "nbt": "" }, { "id": 39, "damage": 0, "count": 1, "nbt": "" }, { "id": 40, "damage": 0, "count": 1, "nbt": "" }, { "id": 99, "damage": 14, "count": 1, "nbt": "" }, { "id": 100, "damage": 14, "count": 1, "nbt": "" }, { "id": 99, "damage": 0, "count": 1, "nbt": "" }, { "id": 99, "damage": 15, "count": 1, "nbt": "" }, { "id": 81, "damage": 0, "count": 1, "nbt": "" }, { "id": 103, "damage": 0, "count": 1, "nbt": "" }, { "id": 86, "damage": 0, "count": 1, "nbt": "" }, { "id": 91, "damage": 0, "count": 1, "nbt": "" }, { "id": 30, "damage": 0, "count": 1, "nbt": "" }, { "id": 170, "damage": 0, "count": 1, "nbt": "" }, { "id": 338, "damage": 0, "count": 1, "nbt": "" }, { "id": 296, "damage": 0, "count": 1, "nbt": "" }, { "id": 31, "damage": 1, "count": 1, "nbt": "" }, { "id": 31, "damage": 2, "count": 1, "nbt": "" }, { "id": 32, "damage": 0, "count": 1, "nbt": "" }, { "id": 6, "damage": 0, "count": 1, "nbt": "" }, { "id": 6, "damage": 1, "count": 1, "nbt": "" }, { "id": 6, "damage": 2, "count": 1, "nbt": "" }, { "id": 6, "damage": 3, "count": 1, "nbt": "" }, { "id": 6, "damage": 4, "count": 1, "nbt": "" }, { "id": 6, "damage": 5, "count": 1, "nbt": "" }, { "id": 18, "damage": 0, "count": 1, "nbt": "" }, { "id": 18, "damage": 1, "count": 1, "nbt": "" }, { "id": 18, "damage": 2, "count": 1, "nbt": "" }, { "id": 18, "damage": 3, "count": 1, "nbt": "" }, { "id": 161, "damage": 0, "count": 1, "nbt": "" }, { "id": 161, "damage": 1, "count": 1, "nbt": "" }, { "id": 262, "damage": 6, "count": 1, "nbt": "" }, { "id": 262, "damage": 7, "count": 1, "nbt": "" }, { "id": 262, "damage": 8, "count": 1, "nbt": "" }, { "id": 262, "damage": 9, "count": 1, "nbt": "" }, { "id": 262, "damage": 10, "count": 1, "nbt": "" }, { "id": 262, "damage": 11, "count": 1, "nbt": "" }, { "id": 262, "damage": 12, "count": 1, "nbt": "" }, { "id": 262, "damage": 13, "count": 1, "nbt": "" }, { "id": 262, "damage": 14, "count": 1, "nbt": "" }, { "id": 262, "damage": 15, "count": 1, "nbt": "" }, { "id": 262, "damage": 16, "count": 1, "nbt": "" }, { "id": 262, "damage": 17, "count": 1, "nbt": "" }, { "id": 262, "damage": 18, "count": 1, "nbt": "" }, { "id": 262, "damage": 19, "count": 1, "nbt": "" }, { "id": 262, "damage": 20, "count": 1, "nbt": "" }, { "id": 262, "damage": 21, "count": 1, "nbt": "" }, { "id": 262, "damage": 22, "count": 1, "nbt": "" }, { "id": 262, "damage": 23, "count": 1, "nbt": "" }, { "id": 262, "damage": 24, "count": 1, "nbt": "" }, { "id": 262, "damage": 25, "count": 1, "nbt": "" }, { "id": 262, "damage": 26, "count": 1, "nbt": "" }, { "id": 262, "damage": 27, "count": 1, "nbt": "" }, { "id": 262, "damage": 28, "count": 1, "nbt": "" }, { "id": 262, "damage": 29, "count": 1, "nbt": "" }, { "id": 262, "damage": 30, "count": 1, "nbt": "" }, { "id": 262, "damage": 31, "count": 1, "nbt": "" }, { "id": 262, "damage": 32, "count": 1, "nbt": "" }, { "id": 262, "damage": 33, "count": 1, "nbt": "" }, { "id": 262, "damage": 34, "count": 1, "nbt": "" }, { "id": 262, "damage": 35, "count": 1, "nbt": "" }, { "id": 262, "damage": 36, "count": 1, "nbt": "" }, { "id": 262, "damage": 37, "count": 1, "nbt": "" }, { "id": 220, "damage": 0, "count": 1, "nbt": "" }, { "id": 228, "damage": 0, "count": 1, "nbt": "" }, { "id": 227, "damage": 0, "count": 1, "nbt": "" }, { "id": 235, "damage": 0, "count": 1, "nbt": "" }, { "id": 232, "damage": 0, "count": 1, "nbt": "" }, { "id": 234, "damage": 0, "count": 1, "nbt": "" }, { "id": 221, "damage": 0, "count": 1, "nbt": "" }, { "id": 224, "damage": 0, "count": 1, "nbt": "" }, { "id": 225, "damage": 0, "count": 1, "nbt": "" }, { "id": 233, "damage": 0, "count": 1, "nbt": "" }, { "id": 229, "damage": 0, "count": 1, "nbt": "" }, { "id": 223, "damage": 0, "count": 1, "nbt": "" }, { "id": 231, "damage": 0, "count": 1, "nbt": "" }, { "id": 219, "damage": 0, "count": 1, "nbt": "" }, { "id": 222, "damage": 0, "count": 1, "nbt": "" }, { "id": 226, "damage": 0, "count": 1, "nbt": "" }, { "id": 295, "damage": 0, "count": 1, "nbt": "" }, { "id": 361, "damage": 0, "count": 1, "nbt": "" }, { "id": 362, "damage": 0, "count": 1, "nbt": "" }, { "id": 458, "damage": 0, "count": 1, "nbt": "" }, { "id": 344, "damage": 0, "count": 1, "nbt": "" }, { "id": 260, "damage": 0, "count": 1, "nbt": "" }, { "id": 322, "damage": 0, "count": 1, "nbt": "" }, { "id": 466, "damage": 0, "count": 1, "nbt": "" }, { "id": 349, "damage": 0, "count": 1, "nbt": "" }, { "id": 460, "damage": 0, "count": 1, "nbt": "" }, { "id": 461, "damage": 0, "count": 1, "nbt": "" }, { "id": 462, "damage": 0, "count": 1, "nbt": "" }, { "id": 350, "damage": 0, "count": 1, "nbt": "" }, { "id": 463, "damage": 0, "count": 1, "nbt": "" }, { "id": 367, "damage": 0, "count": 1, "nbt": "" }, { "id": 282, "damage": 0, "count": 1, "nbt": "" }, { "id": 297, "damage": 0, "count": 1, "nbt": "" }, { "id": 319, "damage": 0, "count": 1, "nbt": "" }, { "id": 320, "damage": 0, "count": 1, "nbt": "" }, { "id": 365, "damage": 0, "count": 1, "nbt": "" }, { "id": 366, "damage": 0, "count": 1, "nbt": "" }, { "id": 423, "damage": 0, "count": 1, "nbt": "" }, { "id": 424, "damage": 0, "count": 1, "nbt": "" }, { "id": 363, "damage": 0, "count": 1, "nbt": "" }, { "id": 364, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 391, "damage": 0, "count": 1, "nbt": "" }, { "id": 392, "damage": 0, "count": 1, "nbt": "" }, { "id": 393, "damage": 0, "count": 1, "nbt": "" }, { "id": 394, "damage": 0, "count": 1, "nbt": "" }, { "id": 457, "damage": 0, "count": 1, "nbt": "" }, { "id": 459, "damage": 0, "count": 1, "nbt": "" }, { "id": 354, "damage": 0, "count": 1, "nbt": "" }, { "id": 357, "damage": 0, "count": 1, "nbt": "" }, { "id": 400, "damage": 0, "count": 1, "nbt": "" }, { "id": 411, "damage": 0, "count": 1, "nbt": "" }, { "id": 412, "damage": 0, "count": 1, "nbt": "" }, { "id": 413, "damage": 0, "count": 1, "nbt": "" }, { "id": 432, "damage": 0, "count": 1, "nbt": "" }, { "id": 433, "damage": 0, "count": 1, "nbt": "" }, { "id": 399, "damage": 0, "count": 1, "nbt": "" }, { "id": 420, "damage": 0, "count": 1, "nbt": "" }, { "id": 421, "damage": 0, "count": 1, "nbt": "" }, { "id": 378, "damage": 0, "count": 1, "nbt": "" }, { "id": 369, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 396, "damage": 0, "count": 1, "nbt": "" }, { "id": 382, "damage": 0, "count": 1, "nbt": "" }, { "id": 414, "damage": 0, "count": 1, "nbt": "" }, { "id": 370, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 377, "damage": 0, "count": 1, "nbt": "" }, { "id": 372, "damage": 0, "count": 1, "nbt": "" }, { "id": 289, "damage": 0, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 348, "damage": 0, "count": 1, "nbt": "" }, { "id": 375, "damage": 0, "count": 1, "nbt": "" }, { "id": 376, "damage": 0, "count": 1, "nbt": "" }, { "id": 437, "damage": 0, "count": 1, "nbt": "" }, { "id": 397, "damage": 0, "count": 1, "nbt": "" }, { "id": 397, "damage": 1, "count": 1, "nbt": "" }, { "id": 397, "damage": 2, "count": 1, "nbt": "" }, { "id": 397, "damage": 3, "count": 1, "nbt": "" }, { "id": 397, "damage": 4, "count": 1, "nbt": "" }, { "id": 397, "damage": 5, "count": 1, "nbt": "" }, { "id": 261, "damage": 0, "count": 1, "nbt": "" }, { "id": 346, "damage": 0, "count": 1, "nbt": "" }, { "id": 259, "damage": 0, "count": 1, "nbt": "" }, { "id": 359, "damage": 0, "count": 1, "nbt": "" }, { "id": 347, "damage": 0, "count": 1, "nbt": "" }, { "id": 345, "damage": 0, "count": 1, "nbt": "" }, { "id": 398, "damage": 0, "count": 1, "nbt": "" }, { "id": 328, "damage": 0, "count": 1, "nbt": "" }, { "id": 342, "damage": 0, "count": 1, "nbt": "" }, { "id": 408, "damage": 0, "count": 1, "nbt": "" }, { "id": 407, "damage": 0, "count": 1, "nbt": "" }, { "id": 333, "damage": 0, "count": 1, "nbt": "" }, { "id": 333, "damage": 1, "count": 1, "nbt": "" }, { "id": 333, "damage": 2, "count": 1, "nbt": "" }, { "id": 333, "damage": 3, "count": 1, "nbt": "" }, { "id": 333, "damage": 4, "count": 1, "nbt": "" }, { "id": 333, "damage": 5, "count": 1, "nbt": "" }, { "id": 329, "damage": 0, "count": 1, "nbt": "" }, { "id": 416, "damage": 0, "count": 1, "nbt": "" }, { "id": 417, "damage": 0, "count": 1, "nbt": "" }, { "id": 418, "damage": 0, "count": 1, "nbt": "" }, { "id": 419, "damage": 0, "count": 1, "nbt": "" }, { "id": 390, "damage": 0, "count": 1, "nbt": "" }, { "id": 383, "damage": 15, "count": 1, "nbt": "" }, { "id": 383, "damage": 10, "count": 1, "nbt": "" }, { "id": 383, "damage": 11, "count": 1, "nbt": "" }, { "id": 383, "damage": 12, "count": 1, "nbt": "" }, { "id": 383, "damage": 13, "count": 1, "nbt": "" }, { "id": 383, "damage": 14, "count": 1, "nbt": "" }, { "id": 383, "damage": 28, "count": 1, "nbt": "" }, { "id": 383, "damage": 22, "count": 1, "nbt": "" }, { "id": 383, "damage": 16, "count": 1, "nbt": "" }, { "id": 383, "damage": 19, "count": 1, "nbt": "" }, { "id": 383, "damage": 18, "count": 1, "nbt": "" }, { "id": 383, "damage": 29, "count": 1, "nbt": "" }, { "id": 383, "damage": 23, "count": 1, "nbt": "" }, { "id": 383, "damage": 24, "count": 1, "nbt": "" }, { "id": 383, "damage": 25, "count": 1, "nbt": "" }, { "id": 383, "damage": 26, "count": 1, "nbt": "" }, { "id": 383, "damage": 27, "count": 1, "nbt": "" }, { "id": 383, "damage": 33, "count": 1, "nbt": "" }, { "id": 383, "damage": 38, "count": 1, "nbt": "" }, { "id": 383, "damage": 39, "count": 1, "nbt": "" }, { "id": 383, "damage": 34, "count": 1, "nbt": "" }, { "id": 383, "damage": 48, "count": 1, "nbt": "" }, { "id": 383, "damage": 46, "count": 1, "nbt": "" }, { "id": 383, "damage": 37, "count": 1, "nbt": "" }, { "id": 383, "damage": 35, "count": 1, "nbt": "" }, { "id": 383, "damage": 32, "count": 1, "nbt": "" }, { "id": 383, "damage": 36, "count": 1, "nbt": "" }, { "id": 383, "damage": 47, "count": 1, "nbt": "" }, { "id": 383, "damage": 17, "count": 1, "nbt": "" }, { "id": 383, "damage": 40, "count": 1, "nbt": "" }, { "id": 383, "damage": 45, "count": 1, "nbt": "" }, { "id": 383, "damage": 49, "count": 1, "nbt": "" }, { "id": 383, "damage": 50, "count": 1, "nbt": "" }, { "id": 383, "damage": 55, "count": 1, "nbt": "" }, { "id": 383, "damage": 42, "count": 1, "nbt": "" }, { "id": 383, "damage": 41, "count": 1, "nbt": "" }, { "id": 383, "damage": 43, "count": 1, "nbt": "" }, { "id": 383, "damage": 54, "count": 1, "nbt": "" }, { "id": 383, "damage": 57, "count": 1, "nbt": "" }, { "id": 383, "damage": 104, "count": 1, "nbt": "" }, { "id": 383, "damage": 105, "count": 1, "nbt": "" }, { "id": 97, "damage": 0, "count": 1, "nbt": "" }, { "id": 97, "damage": 1, "count": 1, "nbt": "" }, { "id": 97, "damage": 2, "count": 1, "nbt": "" }, { "id": 97, "damage": 3, "count": 1, "nbt": "" }, { "id": 97, "damage": 4, "count": 1, "nbt": "" }, { "id": 97, "damage": 5, "count": 1, "nbt": "" }, { "id": 384, "damage": 0, "count": 1, "nbt": "" }, { "id": 385, "damage": 0, "count": 1, "nbt": "" }, { "id": 268, "damage": 0, "count": 1, "nbt": "" }, { "id": 290, "damage": 0, "count": 1, "nbt": "" }, { "id": 269, "damage": 0, "count": 1, "nbt": "" }, { "id": 270, "damage": 0, "count": 1, "nbt": "" }, { "id": 271, "damage": 0, "count": 1, "nbt": "" }, { "id": 272, "damage": 0, "count": 1, "nbt": "" }, { "id": 291, "damage": 0, "count": 1, "nbt": "" }, { "id": 273, "damage": 0, "count": 1, "nbt": "" }, { "id": 274, "damage": 0, "count": 1, "nbt": "" }, { "id": 275, "damage": 0, "count": 1, "nbt": "" }, { "id": 267, "damage": 0, "count": 1, "nbt": "" }, { "id": 292, "damage": 0, "count": 1, "nbt": "" }, { "id": 256, "damage": 0, "count": 1, "nbt": "" }, { "id": 257, "damage": 0, "count": 1, "nbt": "" }, { "id": 258, "damage": 0, "count": 1, "nbt": "" }, { "id": 276, "damage": 0, "count": 1, "nbt": "" }, { "id": 293, "damage": 0, "count": 1, "nbt": "" }, { "id": 277, "damage": 0, "count": 1, "nbt": "" }, { "id": 278, "damage": 0, "count": 1, "nbt": "" }, { "id": 279, "damage": 0, "count": 1, "nbt": "" }, { "id": 283, "damage": 0, "count": 1, "nbt": "" }, { "id": 294, "damage": 0, "count": 1, "nbt": "" }, { "id": 284, "damage": 0, "count": 1, "nbt": "" }, { "id": 285, "damage": 0, "count": 1, "nbt": "" }, { "id": 286, "damage": 0, "count": 1, "nbt": "" }, { "id": 298, "damage": 0, "count": 1, "nbt": "" }, { "id": 299, "damage": 0, "count": 1, "nbt": "" }, { "id": 300, "damage": 0, "count": 1, "nbt": "" }, { "id": 301, "damage": 0, "count": 1, "nbt": "" }, { "id": 302, "damage": 0, "count": 1, "nbt": "" }, { "id": 303, "damage": 0, "count": 1, "nbt": "" }, { "id": 304, "damage": 0, "count": 1, "nbt": "" }, { "id": 305, "damage": 0, "count": 1, "nbt": "" }, { "id": 306, "damage": 0, "count": 1, "nbt": "" }, { "id": 307, "damage": 0, "count": 1, "nbt": "" }, { "id": 308, "damage": 0, "count": 1, "nbt": "" }, { "id": 309, "damage": 0, "count": 1, "nbt": "" }, { "id": 310, "damage": 0, "count": 1, "nbt": "" }, { "id": 311, "damage": 0, "count": 1, "nbt": "" }, { "id": 312, "damage": 0, "count": 1, "nbt": "" }, { "id": 313, "damage": 0, "count": 1, "nbt": "" }, { "id": 314, "damage": 0, "count": 1, "nbt": "" }, { "id": 315, "damage": 0, "count": 1, "nbt": "" }, { "id": 316, "damage": 0, "count": 1, "nbt": "" }, { "id": 317, "damage": 0, "count": 1, "nbt": "" }, { "id": 444, "damage": 0, "count": 1, "nbt": "" }, { "id": 445, "damage": 0, "count": 1, "nbt": "" }, { "id": 450, "damage": 0, "count": 1, "nbt": "" }, { "id": 69, "damage": 0, "count": 1, "nbt": "" }, { "id": 123, "damage": 0, "count": 1, "nbt": "" }, { "id": 76, "damage": 0, "count": 1, "nbt": "" }, { "id": 72, "damage": 0, "count": 1, "nbt": "" }, { "id": 70, "damage": 0, "count": 1, "nbt": "" }, { "id": 147, "damage": 0, "count": 1, "nbt": "" }, { "id": 148, "damage": 0, "count": 1, "nbt": "" }, { "id": 143, "damage": 5, "count": 1, "nbt": "" }, { "id": 77, "damage": 5, "count": 1, "nbt": "" }, { "id": 151, "damage": 0, "count": 1, "nbt": "" }, { "id": 131, "damage": 0, "count": 1, "nbt": "" }, { "id": 356, "damage": 0, "count": 1, "nbt": "" }, { "id": 404, "damage": 0, "count": 1, "nbt": "" }, { "id": 23, "damage": 3, "count": 1, "nbt": "" }, { "id": 125, "damage": 3, "count": 1, "nbt": "" }, { "id": 33, "damage": 1, "count": 1, "nbt": "" }, { "id": 29, "damage": 1, "count": 1, "nbt": "" }, { "id": 251, "damage": 0, "count": 1, "nbt": "" }, { "id": 122, "damage": 0, "count": 1, "nbt": "" }, { "id": 410, "damage": 0, "count": 1, "nbt": "" }, { "id": 332, "damage": 0, "count": 1, "nbt": "" }, { "id": 368, "damage": 0, "count": 1, "nbt": "" }, { "id": 381, "damage": 0, "count": 1, "nbt": "" }, { "id": 426, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 422, "damage": 0, "count": 1, "nbt": "" }, { "id": 52, "damage": 0, "count": 1, "nbt": "" }, { "id": 116, "damage": 0, "count": 1, "nbt": "" }, { "id": 165, "damage": 0, "count": 1, "nbt": "" }, { "id": 130, "damage": 0, "count": 1, "nbt": "" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0000\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0000\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0000\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0000\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0001\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0001\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0001\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0001\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0002\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0002\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0002\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0002\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0003\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0003\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0003\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0003\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0004\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0004\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0004\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0004\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0005\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0005\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0005\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0006\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0006\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0006\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0007\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0007\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0007\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\b\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\t\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\t\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\t\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\t\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\t\u0000\u0002\u0003\u0000lvl\u0005\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\n\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\n\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\n\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\n\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\n\u0000\u0002\u0003\u0000lvl\u0005\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000b\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000b\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000b\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000b\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000b\u0000\u0002\u0003\u0000lvl\u0005\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\f\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\f\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\r\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\r\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000e\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000e\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000e\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000f\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000f\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000f\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000f\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u000f\u0000\u0002\u0003\u0000lvl\u0005\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0010\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0011\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0011\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0011\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0012\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0012\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0012\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0013\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0013\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0013\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0013\u0000\u0002\u0003\u0000lvl\u0004\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0013\u0000\u0002\u0003\u0000lvl\u0005\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0014\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0014\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0015\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0016\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0017\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0017\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0017\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0018\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0018\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0018\u0000\u0002\u0003\u0000lvl\u0003\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0019\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u0019\u0000\u0002\u0003\u0000lvl\u0002\u0000\u0000\u0000" }, { "id": 403, "damage": 0, "count": 1, "nbt": "\n\u0000\u0000\t\u0004\u0000ench\n\u0001\u0000\u0000\u0000\u0002\u0002\u0000id\u001a\u0000\u0002\u0003\u0000lvl\u0001\u0000\u0000\u0000" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" }, { "id": 35, "damage": 8, "count": 1, "nbt": "" }, { "id": 35, "damage": 7, "count": 1, "nbt": "" }, { "id": 35, "damage": 15, "count": 1, "nbt": "" }, { "id": 35, "damage": 12, "count": 1, "nbt": "" }, { "id": 35, "damage": 14, "count": 1, "nbt": "" }, { "id": 35, "damage": 1, "count": 1, "nbt": "" }, { "id": 35, "damage": 4, "count": 1, "nbt": "" }, { "id": 35, "damage": 5, "count": 1, "nbt": "" }, { "id": 35, "damage": 13, "count": 1, "nbt": "" }, { "id": 35, "damage": 9, "count": 1, "nbt": "" }, { "id": 35, "damage": 3, "count": 1, "nbt": "" }, { "id": 35, "damage": 11, "count": 1, "nbt": "" }, { "id": 35, "damage": 10, "count": 1, "nbt": "" }, { "id": 35, "damage": 2, "count": 1, "nbt": "" }, { "id": 35, "damage": 6, "count": 1, "nbt": "" }, { "id": 171, "damage": 0, "count": 1, "nbt": "" }, { "id": 171, "damage": 8, "count": 1, "nbt": "" }, { "id": 171, "damage": 7, "count": 1, "nbt": "" }, { "id": 171, "damage": 15, "count": 1, "nbt": "" }, { "id": 171, "damage": 12, "count": 1, "nbt": "" }, { "id": 171, "damage": 14, "count": 1, "nbt": "" }, { "id": 171, "damage": 1, "count": 1, "nbt": "" }, { "id": 171, "damage": 4, "count": 1, "nbt": "" }, { "id": 171, "damage": 5, "count": 1, "nbt": "" }, { "id": 171, "damage": 13, "count": 1, "nbt": "" }, { "id": 171, "damage": 9, "count": 1, "nbt": "" }, { "id": 171, "damage": 3, "count": 1, "nbt": "" }, { "id": 171, "damage": 11, "count": 1, "nbt": "" }, { "id": 171, "damage": 10, "count": 1, "nbt": "" }, { "id": 171, "damage": 2, "count": 1, "nbt": "" }, { "id": 171, "damage": 6, "count": 1, "nbt": "" }, { "id": 351, "damage": 0, "count": 1, "nbt": "" }, { "id": 351, "damage": 8, "count": 1, "nbt": "" }, { "id": 351, "damage": 7, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" }, { "id": 351, "damage": 12, "count": 1, "nbt": "" }, { "id": 351, "damage": 14, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" }, { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 351, "damage": 5, "count": 1, "nbt": "" }, { "id": 351, "damage": 13, "count": 1, "nbt": "" }, { "id": 351, "damage": 9, "count": 1, "nbt": "" }, { "id": 351, "damage": 3, "count": 1, "nbt": "" }, { "id": 351, "damage": 11, "count": 1, "nbt": "" }, { "id": 351, "damage": 10, "count": 1, "nbt": "" }, { "id": 351, "damage": 2, "count": 1, "nbt": "" }, { "id": 351, "damage": 6, "count": 1, "nbt": "" }, { "id": 237, "damage": 0, "count": 1, "nbt": "" }, { "id": 237, "damage": 8, "count": 1, "nbt": "" }, { "id": 237, "damage": 7, "count": 1, "nbt": "" }, { "id": 237, "damage": 15, "count": 1, "nbt": "" }, { "id": 237, "damage": 12, "count": 1, "nbt": "" }, { "id": 237, "damage": 14, "count": 1, "nbt": "" }, { "id": 237, "damage": 1, "count": 1, "nbt": "" }, { "id": 237, "damage": 4, "count": 1, "nbt": "" }, { "id": 237, "damage": 5, "count": 1, "nbt": "" }, { "id": 237, "damage": 13, "count": 1, "nbt": "" }, { "id": 237, "damage": 9, "count": 1, "nbt": "" }, { "id": 237, "damage": 3, "count": 1, "nbt": "" }, { "id": 237, "damage": 11, "count": 1, "nbt": "" }, { "id": 237, "damage": 10, "count": 1, "nbt": "" }, { "id": 237, "damage": 2, "count": 1, "nbt": "" }, { "id": 237, "damage": 6, "count": 1, "nbt": "" }, { "id": 236, "damage": 0, "count": 1, "nbt": "" }, { "id": 236, "damage": 8, "count": 1, "nbt": "" }, { "id": 236, "damage": 7, "count": 1, "nbt": "" }, { "id": 236, "damage": 15, "count": 1, "nbt": "" }, { "id": 236, "damage": 12, "count": 1, "nbt": "" }, { "id": 236, "damage": 14, "count": 1, "nbt": "" }, { "id": 236, "damage": 1, "count": 1, "nbt": "" }, { "id": 236, "damage": 4, "count": 1, "nbt": "" }, { "id": 236, "damage": 5, "count": 1, "nbt": "" }, { "id": 236, "damage": 13, "count": 1, "nbt": "" }, { "id": 236, "damage": 9, "count": 1, "nbt": "" }, { "id": 236, "damage": 3, "count": 1, "nbt": "" }, { "id": 236, "damage": 11, "count": 1, "nbt": "" }, { "id": 236, "damage": 10, "count": 1, "nbt": "" }, { "id": 236, "damage": 2, "count": 1, "nbt": "" }, { "id": 236, "damage": 6, "count": 1, "nbt": "" }, { "id": 374, "damage": 0, "count": 1, "nbt": "" }, { "id": 373, "damage": 0, "count": 1, "nbt": "" }, { "id": 373, "damage": 1, "count": 1, "nbt": "" }, { "id": 373, "damage": 2, "count": 1, "nbt": "" }, { "id": 373, "damage": 3, "count": 1, "nbt": "" }, { "id": 373, "damage": 4, "count": 1, "nbt": "" }, { "id": 373, "damage": 5, "count": 1, "nbt": "" }, { "id": 373, "damage": 6, "count": 1, "nbt": "" }, { "id": 373, "damage": 7, "count": 1, "nbt": "" }, { "id": 373, "damage": 8, "count": 1, "nbt": "" }, { "id": 373, "damage": 9, "count": 1, "nbt": "" }, { "id": 373, "damage": 10, "count": 1, "nbt": "" }, { "id": 373, "damage": 11, "count": 1, "nbt": "" }, { "id": 373, "damage": 12, "count": 1, "nbt": "" }, { "id": 373, "damage": 13, "count": 1, "nbt": "" }, { "id": 373, "damage": 14, "count": 1, "nbt": "" }, { "id": 373, "damage": 15, "count": 1, "nbt": "" }, { "id": 373, "damage": 16, "count": 1, "nbt": "" }, { "id": 373, "damage": 17, "count": 1, "nbt": "" }, { "id": 373, "damage": 18, "count": 1, "nbt": "" }, { "id": 373, "damage": 19, "count": 1, "nbt": "" }, { "id": 373, "damage": 20, "count": 1, "nbt": "" }, { "id": 373, "damage": 21, "count": 1, "nbt": "" }, { "id": 373, "damage": 22, "count": 1, "nbt": "" }, { "id": 373, "damage": 23, "count": 1, "nbt": "" }, { "id": 373, "damage": 24, "count": 1, "nbt": "" }, { "id": 373, "damage": 25, "count": 1, "nbt": "" }, { "id": 373, "damage": 26, "count": 1, "nbt": "" }, { "id": 373, "damage": 27, "count": 1, "nbt": "" }, { "id": 373, "damage": 28, "count": 1, "nbt": "" }, { "id": 373, "damage": 29, "count": 1, "nbt": "" }, { "id": 373, "damage": 30, "count": 1, "nbt": "" }, { "id": 373, "damage": 31, "count": 1, "nbt": "" }, { "id": 373, "damage": 32, "count": 1, "nbt": "" }, { "id": 373, "damage": 33, "count": 1, "nbt": "" }, { "id": 373, "damage": 34, "count": 1, "nbt": "" }, { "id": 373, "damage": 35, "count": 1, "nbt": "" }, { "id": 373, "damage": 36, "count": 1, "nbt": "" }, { "id": 438, "damage": 0, "count": 1, "nbt": "" }, { "id": 438, "damage": 1, "count": 1, "nbt": "" }, { "id": 438, "damage": 2, "count": 1, "nbt": "" }, { "id": 438, "damage": 3, "count": 1, "nbt": "" }, { "id": 438, "damage": 4, "count": 1, "nbt": "" }, { "id": 438, "damage": 5, "count": 1, "nbt": "" }, { "id": 438, "damage": 6, "count": 1, "nbt": "" }, { "id": 438, "damage": 7, "count": 1, "nbt": "" }, { "id": 438, "damage": 8, "count": 1, "nbt": "" }, { "id": 438, "damage": 9, "count": 1, "nbt": "" }, { "id": 438, "damage": 10, "count": 1, "nbt": "" }, { "id": 438, "damage": 11, "count": 1, "nbt": "" }, { "id": 438, "damage": 12, "count": 1, "nbt": "" }, { "id": 438, "damage": 13, "count": 1, "nbt": "" }, { "id": 438, "damage": 14, "count": 1, "nbt": "" }, { "id": 438, "damage": 15, "count": 1, "nbt": "" }, { "id": 438, "damage": 16, "count": 1, "nbt": "" }, { "id": 438, "damage": 17, "count": 1, "nbt": "" }, { "id": 438, "damage": 18, "count": 1, "nbt": "" }, { "id": 438, "damage": 19, "count": 1, "nbt": "" }, { "id": 438, "damage": 20, "count": 1, "nbt": "" }, { "id": 438, "damage": 21, "count": 1, "nbt": "" }, { "id": 438, "damage": 22, "count": 1, "nbt": "" }, { "id": 438, "damage": 23, "count": 1, "nbt": "" }, { "id": 438, "damage": 24, "count": 1, "nbt": "" }, { "id": 438, "damage": 25, "count": 1, "nbt": "" }, { "id": 438, "damage": 26, "count": 1, "nbt": "" }, { "id": 438, "damage": 27, "count": 1, "nbt": "" }, { "id": 438, "damage": 28, "count": 1, "nbt": "" }, { "id": 438, "damage": 29, "count": 1, "nbt": "" }, { "id": 438, "damage": 30, "count": 1, "nbt": "" }, { "id": 438, "damage": 31, "count": 1, "nbt": "" }, { "id": 438, "damage": 32, "count": 1, "nbt": "" }, { "id": 438, "damage": 33, "count": 1, "nbt": "" }, { "id": 438, "damage": 34, "count": 1, "nbt": "" }, { "id": 438, "damage": 35, "count": 1, "nbt": "" }, { "id": 438, "damage": 36, "count": 1, "nbt": "" }, { "id": 441, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 1, "count": 1, "nbt": "" }, { "id": 441, "damage": 2, "count": 1, "nbt": "" }, { "id": 441, "damage": 3, "count": 1, "nbt": "" }, { "id": 441, "damage": 4, "count": 1, "nbt": "" }, { "id": 441, "damage": 5, "count": 1, "nbt": "" }, { "id": 441, "damage": 6, "count": 1, "nbt": "" }, { "id": 441, "damage": 7, "count": 1, "nbt": "" }, { "id": 441, "damage": 8, "count": 1, "nbt": "" }, { "id": 441, "damage": 9, "count": 1, "nbt": "" }, { "id": 441, "damage": 10, "count": 1, "nbt": "" }, { "id": 441, "damage": 11, "count": 1, "nbt": "" }, { "id": 441, "damage": 12, "count": 1, "nbt": "" }, { "id": 441, "damage": 13, "count": 1, "nbt": "" }, { "id": 441, "damage": 14, "count": 1, "nbt": "" }, { "id": 441, "damage": 15, "count": 1, "nbt": "" }, { "id": 441, "damage": 16, "count": 1, "nbt": "" }, { "id": 441, "damage": 17, "count": 1, "nbt": "" }, { "id": 441, "damage": 18, "count": 1, "nbt": "" }, { "id": 441, "damage": 19, "count": 1, "nbt": "" }, { "id": 441, "damage": 20, "count": 1, "nbt": "" }, { "id": 441, "damage": 21, "count": 1, "nbt": "" }, { "id": 441, "damage": 22, "count": 1, "nbt": "" }, { "id": 441, "damage": 23, "count": 1, "nbt": "" }, { "id": 441, "damage": 24, "count": 1, "nbt": "" }, { "id": 441, "damage": 25, "count": 1, "nbt": "" }, { "id": 441, "damage": 26, "count": 1, "nbt": "" }, { "id": 441, "damage": 27, "count": 1, "nbt": "" }, { "id": 441, "damage": 28, "count": 1, "nbt": "" }, { "id": 441, "damage": 29, "count": 1, "nbt": "" }, { "id": 441, "damage": 30, "count": 1, "nbt": "" }, { "id": 441, "damage": 31, "count": 1, "nbt": "" }, { "id": 441, "damage": 32, "count": 1, "nbt": "" }, { "id": 441, "damage": 33, "count": 1, "nbt": "" }, { "id": 441, "damage": 34, "count": 1, "nbt": "" }, { "id": 441, "damage": 35, "count": 1, "nbt": "" }, { "id": 441, "damage": 36, "count": 1, "nbt": "" } ]# # _____ _ _____ # / ____| (_) | __ \ # | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ # | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ # | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | # \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ # __/ | # |___/ # # # #Genisys高级配置文件 #配置文件版本 config: version: 28 level: #设置是否变换天气 weather: true #随机天气持续时长最小值,最大值 weather-random-duration-min: 6000 weather-random-duration-max: 12000 #随机闪电间隔,默认10秒,0 = 禁用 lightning-time: 200 #是否启用闪电击中着火 lightning-fire: false #是否启用火焰蔓延 fire-spread: false player: #是否打开饥饿 hunger: true #是否打开经验系统 experience: true #是否开启死亡不掉落 keep-inventory: false #是否开启切换模式自动清除背包 auto-clear-inventory: true #是否开启死亡经验不掉落 keep-experience: false #如果玩家进入游戏时崩溃, 请设置低于10的值. 禁用 = -1 chunk-radius: -1 developer: #是否允许服务器加载文件夹插件(源码) #建议关闭 folder-plugin-loader: true #是否允许服务器加载不兼容的API插件 #建议关闭 load-incompatible-api: true nether: #是否允许地狱,打开此选项会自动生成地狱地图 allow-nether: true #地狱地图名 level-name: "nether" ender: #是否允许末地,打开此选项会自动生成下界地图 allow-ender: true #末地地图名 level-name: "ender" server: #是否允许生成铁傀儡 allow-iron-golem: false #是否允许生成雪傀儡 allow-snow-golem: false #是否禁用server.log disable-log: false #是否启用反飞行作弊 anti-fly: true #是否启用异步方式发送区块 async-chunk-request: true #玩家进出服务器消息提醒方式。0为Message,1为Tip,2为Popup player-msg-type: 0 login-msg: "§3@player 加入了游戏" logout-msg: "§3@player 退出了游戏" #是否限制创造某些功能(禁止丢物品, 禁止操作箱子等等) limited-creative: false #是否开启方块破坏粒子 destroy-block-particle: true #是否允许喷溅型药水 allow-splash-potion: true #是否启用高级指令选择器 advanced-command-selector: false #是否加载ResourcePackManager enable-resource: false #是否开启海绵的吸水功能 absorb-water: false enchantment: #是否允许使用铁砧 enable-anvil: true #是否允许使用附魔台 enable-enchanting-table: true #是否启用计算附魔等级(计算书架数量),可能造成服务器延迟 #如果不启用本项, 附魔等级将在0-15间随机选取 count-bookshelf: false redstone: ############################## #######是否开启红石系统####### ############################## #如果不改为true将无法使用红石# ############################## enable: false #是否允许频率脉冲 frequency-pulse: false #设置脉冲频率, 默认: 1s pulse-frequency: 1 dserver: #多服统统一人数 enable: false #Query自动更新 query-auto-update: false #Query周期更新 query-tick-update: true #Motd最大人数,0为默认 motd-max-players: 0 #Query最大人数,0为默认 query-max-players: 0 #Motd显示总人数 motd-all-players: false #Query显示总人数 query-all-players: false #Motd显示人数 motd-players: false #Query显示人数 query-players: false #更新频率,20 = 1秒 time: 40 #获取失败自动重试次数 retry-times: 3 #服务器列表,用;隔开,例如 1.example.com:19132;2.example.com:19133 server-list: "" inventory: #如果无法使用铁砧或附魔台,请启用本项. 将会对背包交易进行验证. allow-cheats: false# # _____ _ _____ # / ____| (_) | __ \ # | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ # | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ # | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | # \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ # __/ | # |___/ # # # #GenisysPro Advanced Configuration File #Version of this file config: version: 28 level: #Set if weather is enabled (rain may cause lag to older devices) weather: true #Weather random duration weather-random-duration-min: 6000 weather-random-duration-max: 12000 #Random lightning interval,default as 10s, 0 = disable lightning-time: 200 #Set if lightning strikes have fire afterwards lightning-fire: false #Set if fire should spread (trees, etc.) fire-spread: false player: #Set if hunger is enabled hunger: true #Choose if experience is enabled experience: true #Choose if to keep a player's inventory after they die keep-inventory: false #Clear the inventory of a player upon them changing their gamemode auto-clear-inventory : true #Choose if to keep a player's experience after they die keep-experience: false developer: #This setting allows the server to load plugins through the source directory over a .phar file #Recommended that it stays "false" folder-plugin-loader: true #This setting allows the server to load plugins with Incompatible API #Recommanded that it stays "false" load-incompatible-api: true nether: #Choose if the nether is allowed. The level of nether will generate automatically allow-nether: true #The name of nether's level level-name: "nether" ender: allow-ender: true level-name: "ender" server: #Choose if spawning iron golem is allowed allow-iron-golem: false #Choose if spawning snow golem is allowed allow-snow-golem: false #Choose if server.log is disabled disable-log: false #Choose how to remind players when someone joins the game #0 = Message, 1 = Tip, 2 = Popup player-msg-type: 0 login-msg: "§3@player joined the game" logout-msg: "§3@player left the game" #Set if limited creative is enabled (cannot drop items from hotbar, cannot open chests, and so on) limited-creative: false #Set if add DestroyBlockParticle destroy-block-particle: true #Set if splash potions are enabled allow-splash-potion: true #Set if Advanced Command Selector is enabled advanced-command-selector: false #Set if enable ResourcePackManager enable-resource: false #This parameter turns on or off the absorption of water by a sponge absorb-water: false enchantment: #Choose if anvils are enabled enable-anvil: true #Choose if enchantment tables are enabled enable-enchanting-table: true #Choose if to count bookshelves (may cause server lag) #If this option is false, the server will use a random count (0~15) count-bookshelf: false redstone: ################################################ ####Choose if the redstone system is enabled#### ################################################ #If it is false, the redstone system won't work# ################################################ enable: false #Choose if frequency pulses are enabled frequency-pulse: false #Set the frequency of pulse. Default = 1s pulse-frequency: 1 dserver: #The count of all multi-server unified enable: false #Update Query automatically query-auto-update: false #Update Query periodically query-tick-update: true #The max players' on the MOTD motd-max-players: 0 #The max players' on the Query. 0=Default query-max-players: 0 #Show the number of all players on MOTD motd-all-players: false #Show the number of all players on Query query-all-players: false #Show the number of online players on MOTD motd-players: false #Show the number of online players on Query query-players: false #Update Frequency. 20=1s time: 40 #Auto-retry # of times when server fails retry-times: 3 #the server list,Separate by ';',e.g. 1.example.com:19132;2.example.com:19133 server-list: "" inventory: #Set this to true if you have problems with anvils. This will process inventory transactions in a vanilla fashion with no anti-cheats or verification. allow-cheats: false # _____ _ _____ # / ____| (_) | __ \ # | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ # | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ # | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | # \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ # __/ | # |___/ # # # #Fichier de configuration avancé de GenisysPro #Version de ce fichier config: version: 28 level: #Réglez si la météo est activée (la pluie peut provoquer un retard sur les périphériques plus anciens) weather: true #Temps de durée aléatoire weather-random-duration-min: 6000 weather-random-duration-max: 12000 #Intervalle d'éclairement aléatoire, par défaut 10s, 0 = désactiver lightning-time: 200 #Réglez si les éclairs mettent le feu dans les biomes après lightning-fire: false #Réglez si le feu doit se propager (arbres, etc.) fire-spread: false player: #Réglez si la faim est activée hunger: true #Choisissez si l'expérience est activée experience: true #Choisissez de garder l'inventaire d'un joueur après sa mort keep-inventory: false #Effacez l'inventaire d'un joueur en changeant leur gamemode auto-clear-inventory : true #Choisissez de garder l'expérience keep-experience: false developer: #Ce paramètre permet au serveur de charger des plugins via le répertoire source via un fichier .phar #Recommandé qu'il reste "faux" folder-plugin-loader: true #Ce paramètre permet au serveur de charger des plugins avec une API incompatible #Recommandé qu'il reste "faux" load-incompatible-api: true nether: #Choisissez si le nether est autorisé, le niveau de Nether générera automatiquement allow-nether: true #Recommandé qu'il reste "nether" level-name: "nether" ender: allow-ender: true level-name: "ender" server: #Choisissez si le golem de génie est autorisé allow-iron-golem: false #Choisissez si le vent de neige qui frappe est autorisé allow-snow-golem: false #Choisissez si server.log est activé/désactivé disable-log: false #Choisissez comment appeler les joueurs lorsque qu'ils rejoignent la partie #0 = Message, 1 = Pointe, 2 = Apparaitre player-msg-type: 0 login-msg: "§3@player rejoint la partie" logout-msg: "§3@player quitte la partie" #Définir si la création limitée est activée (ne peut pas supprimer les éléments de la barre hotbar, ne pas ouvrir les coffres, etc.) limited-creative: false #Définir si ajouter DestroyBlockParticle destroy-block-particle: true #Réglez si les potions d'éclaboussures sont activées allow-splash-potion: true # Réglez si les commande de selection sont activées advanced-command-selector: false #Définir si il faut activer ResourcePackManager enable-resource: false #This parameter turns on or off the absorption of water by a sponge //TODO: Translation for other languages absorb-water: false enchantment: #Choisissez si les anvil sont activées enable-anvil: true #Choisissez si les tables d'enchantement sont activées enable-enchanting-table: true #Choisissez de compter les étagères (peut causer un retard de serveur) #Si cette option est fausse, le serveur utilisera un compte aléatoire (0 à 15) count-bookshelf: false redstone: ########################################################## # Choisissez si le système redstone est activé # ########################################################## # Si c'est faux, le système Redstone ne fonctionnera pas # ########################################################## enable: false #Choisissez si les impulsions de fréquence sont activées frequency-pulse: false #Si il et activé, réglez la fréquence des impulsions. Par défaut = 1s pulse-frequency: 1 dserver: #Le nombre de tous les serveurs multi-serveurs unifiés enable: false #Mettre à jour automatiquement la requête query-auto-update: false #Mettre à jour périodiquement la requête query-tick-update: true #Le maximum de joueurs sur le MOTD motd-max-players: 0 #Les joueurs maximum sur la requête. 0 = par défaut query-max-players: 0 #Afficher le nombre de tous les joueurs sur MOTD motd-all-players: false #Afficher le nombre de tous les joueurs sur la requête query-all-players: false #Afficher le nombre de joueurs en ligne sur MOTD motd-players: false #Afficher le nombre de joueurs en ligne sur la requête query-players: false #Fréquence de mise à jour. 20 = 1s time: 40 #Réessayez automatiquement le nombre de fois où le serveur échoue retry-times: 3 #La liste du serveur, séparée par ';', par exemple 1.example.com:19132 ;2.example.com:19133 server-list: "" inventory: #Configurez ceci comme vrai si vous avez des problèmes avec les enclumes. Cela permettra de traiter les transactions d'inventaire de manière vanille sans anti-triche ou de vérification. allow-cheats: false # # _____ _ _____ # / ____| (_) | __ \ # | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ # | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ # | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | # \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ # __/ | # |___/ # # # #GenisysPro 詳細設定ファイル #true:有効 #false:無効 #1秒:20tick #ファイルのバージョン config: version: 28 level: #有効にすると、天候要素が加わります(雨が降ると古い端末の動作が遅くなる場合があります) weather: true #天候が変わる時間の間隔(min:最小値 max:最大値) weather-random-duration-min: 6000 weather-random-duration-max: 12000 #落雷が発生する間隔(初期値は10秒、0秒にすると無効になります) lightning-time: 200 #有効にすると落雷した場所で火災が発生します lightning-fire: false #有効にすると炎が延焼します(木など) fire-spread: false player: #有効にすると満腹度要素が機能します hunger: true #有効にすると経験値要素が機能します experience: true #有効にするとプレイヤーが倒れてもインベントリ(プレイヤーの持っている、ブロックやアイテム)を維持します keep-inventory: false #有効にするとプレイヤーがゲームモードを変更したときにインベントリを消去します auto-clear-inventory : true #有効にするとプレイヤーが倒れても経験値を維持します keep-experience: false developer: #有効にするとソースディレクトリからプラグインを読み込みます #設定を変更しないことを推奨します folder-plugin-loader: true #有効にすると互換性のないAPIのプラグインを読み込みます #設定を変更しないことを推奨します load-incompatible-api: true nether: #有効(true)にするとネザー(nether)ワールド(Level)が自動的に生成され、ポータルを用いて移動できます allow-nether: true #ネザーのワールド(Level)名の設定 level-name: "nether" ender: #有効(true)にするとジ・エンド(ender)ワールド(Level)が自動的に生成され、ポータルを用いて移動できます allow-ender: true #ジ・エンドのワールド(Level)名の設定 level-name: "ender" server: #有効にするとアイアンゴーレムのスポーンを許可します allow-iron-golem: false #有効にするとスノーゴーレムのスポーンを許可します allow-snow-golem: false #有効にするとserver.logファイルにログ内容を保存しません disable-log: false #プレイヤーが参加または退出したときにお知らせする方法 #0 = Message, 1 = Tip, 2 = Popup player-msg-type: 0 login-msg: "§3@player が参加しました" logout-msg: "§3@player が退出しました" #有効にするとクリエイティブモードを制限します(インベントリからアイテムをドロップできず、チェストを開けることができません) limited-creative: false #有効にするとブロックを破壊したときにパーティクルを表示します destroy-block-particle: true #有効にするとスプラッシュポーションが使用可能になります allow-splash-potion: true #有効にすると高度なコマンドセレクタが使用可能になります advanced-command-selector: false #有効にするとResourcePackManagerが使用可能になります enable-resource: false #スポンジブロックによる水の吸収機能の使用を設定できます absorb-water: false enchantment: #有効にすると金床が使用可能になります enable-anvil: true #有効にするとエンチャントテーブルが使用可能になります enable-enchanting-table: true #有効にすると周りの本棚を数えます(有効にするとサーバーの動作が遅くなる場合があります) #無効にすると本棚の数を0~15の間でランダムに決めます count-bookshelf: false redstone: ############################################## ####有効にするとレッドストーンが動作します#### ############################################## ###無効にするとレッドストーンが動作しません### ############################################## enable: false #有効にすると周波数パルスを設定します frequency-pulse: false #周波数パルスの間隔(初期値は1秒) pulse-frequency: 1 dserver: #有効にすると全てのマルチサーバーの数を統一します enable: false #有効にするとクエリを自動的に更新します query-auto-update: false #有効にするとクエリを定期的に更新します query-tick-update: true #MOTDに表示するプレイヤーの最大人数 motd-max-players: 0 #クエリーに表示するプレイヤーの最大人数(初期値は0人) query-max-players: 0 #有効にするとMOTDにプレイヤーの人数を表示します motd-all-players: false #有効にするとクエリーにプレイヤーの人数を表示します query-all-players: false #有効にするとMOTDにプレイヤーのオンライン人数を表示します motd-players: false #有効にするとクエリーにプレイヤーのオンライン人数を表示します query-players: false #更新する間隔(20で1秒) time: 40 #失敗したときに再試行する間隔 retry-times: 3 #サーバーリスト(「;」で区切ることができます、例:1.example.com:19132;2.example.com:19133) server-list: "" inventory: #金床に問題がある場合は無効にすると、チートの検証をせずにインベントリを処理します allow-cheats: false # # _____ _ _____ # / ____| (_) | __ \ # | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ # | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ # | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | # \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ # __/ | # |___/ # # # #Расширенный файл конфигурации GenisysPro #true (прада) - включено #false (ложь) - выключено #Хотите улучшить перевод? #Создайте пулл на github или напишите мне в Telegram #Github: https://github.com/GenisysPro/GenisysPro/pulls #Telegram: https://t.me/Yarvoy #Версия этого файла config: version: 28 level: #Выберите, включена ли погода (дождь может вызвать лаги на старых устройствах) weather: true #Случаная продолжительность погоды weather-random-duration-min: 6000 weather-random-duration-max: 12000 #Случайный интервал молнии, по умолчанию как 10 сек, 0 = отключить lightning-time: 200 #Пожег при ударе молнии lightning-fire: false #Распространение огня fire-spread: false player: #Выберите, включена ли система голода hunger: true #Выберите, включена ли система опыта experience: true #Сохранение инвентаря после смерти keep-inventory: false #Очистка инвентаря при смене режима игры auto-clear-inventory : true #Сохранение опыта после смерти keep-experience: false developer: #Этот параметр позволяет серверу загружать плагины из исходников #Рекомендуется, чтобы он оставался "ложным" folder-plugin-loader: true #Этот параметр отключает проверку API версии плагинов #Рекомендуется, чтобы он оставался "ложным" load-incompatible-api: true nether: #Нижний мир (ад) allow-nether: true #Имя мира level-name: "nether" ender: #Край allow-ender: true #Имя мира level-name: "ender" server: #Разрешает спавн железных големов allow-iron-golem: false #Разрешает спавн снежных големов allow-snow-golem: false #Отключает ведение лога (server.log) disable-log: false #Выберите, как напомнить игрокам, когда кто-то присоединяется к игре #0 = Message, 1 = Tip, 2 = Popup player-msg-type: 0 login-msg: "§3@player joined the game" logout-msg: "§3@player left the game" #Ограниченный творческий режим limited-creative: false #Отображает частицы блока при разрушении destroy-block-particle: true #Взрывающиеся зелья allow-splash-potion: true #Эта настройка решает, включен ли расширенный выбор команд advanced-command-selector: false #Выберите, разрешены ли ресурспаки enable-resource: false #Этот параметр включает или отключает поглощение воды губкой absorb-water: false enchantment: #Выберите, разрешены ли наковальни enable-anvil: true #Выберите, включены ли столы зачарования enable-enchanting-table: true #Вы можете включить улучшение от книжных полок (если выключено, используется рандомное количество от 0 до 15), рекомендуется этого не делать, это может быть причиной нагрузки count-bookshelf: false redstone: #Выберите, включена ли система редстоуна enable: false #Выберите, если включены импульсы частоты frequency-pulse: false #Установите частоту импульса. По умолчанию = 1 сек pulse-frequency: 1 dserver: #Разрешает использовать мульти сервера enable: false #Обновление query автоматически query-auto-update: false #Периодически обновлять query query-tick-update: true #Отображает максимум игроков в MOTD. 0 = По умолчанию motd-max-players: 0 #Отображает максимум игроков в Query. 0 = По умолчанию query-max-players: 0 #Отображает всех игроков в MOTD motd-all-players: false #Отображает количество всех игроков в Query query-all-players: false #Отображает количество онлайн игроков в MOTD motd-players: false #Отображает количество онлайн игроков в Query query-players: false #Частота обновления. 20 = 1 сек time: 40 #Авто-повтор N раз при сбое сервера retry-times: 3 #Список серверов, отделяйте через ';', пример: 1.example.com:19132;2.example.com:19133 server-list: "" inventory: #Установите значение true, если у вас проблемы с наковальнями. Это будет обрабатывать транзакции инвентаризации ванильным способом без анти-читов или проверки. allow-cheats: false # # _____ _ _____ # / ____| (_) | __ \ # | | __ ___ _ __ _ ___ _ _ ___| |__) | __ ___ # | | |_ |/ _ \ '_ \| / __| | | / __| ___/ '__/ _ \ # | |__| | __/ | | | \__ \ |_| \__ \ | | | | (_) | # \_____|\___|_| |_|_|___/\__, |___/_| |_| \___/ # __/ | # |___/ # # # #Genisys進階配置檔案 #配置檔案版本 config: version: 28 level: #設定是否變換天氣 weather: true #隨機天氣持續時長最小值,最大值 weather-random-duration-min: 6000 weather-random-duration-max: 12000 #隨機閃電間隔,預設10秒,0 = 停用 lightning-time: 200 #是否啟用閃電擊中著火 lightning-fire: false #是否啟用火焰蔓延 fire-spread: false player: #是否打開饑餓 hunger: true #是否打開經驗系統 experience: true #是否開啟死亡不掉落 keep-inventory: false #是否開啟切換模式自動清除背包 auto-clear-inventory: true #是否開啟死亡經驗不掉落 keep-experience: false #如果玩家進入遊戲時無回應, 請設定低於10的值. 停用 = -1 chunk-radius: -1 developer: #是否允許伺服器讀取檔案夾插件(源碼) #建議關閉 folder-plugin-loader: true #是否允許伺服器讀取不兼容的API插件 #建議關閉 load-incompatible-api: true nether: #是否允許下界,打開此選項會自動生成下界地圖 allow-nether: true #下界地圖名 level-name: "nether" ender: #是否允許末路之地,打開此選項會自動生成末路之地地圖 allow-ender: true #末路之地地圖名 level-name: "ender" server: #是否允許生成鐵傀儡 allow-iron-golem: false #是否允許生成雪傀儡 allow-snow-golem: false #是否停用server.log disable-log: false #是否啟用反飛行作弊 anti-fly: true #是否啟用異步方式發送區塊 async-chunk-request: true #玩家進出伺服器訊息提醒方式。0為Message,1為Tip,2為Popup player-msg-type: 0 login-msg: "§3@player 加入了遊戲" logout-msg: "§3@player 退出了遊戲" #是否限制創造某些功能(禁止丟物品, 禁止操作箱子等等) limited-creative: false #是否開啟方塊破壞粒子 destroy-block-particle: true #是否允許噴濺型藥水 allow-splash-potion: true #是否啟用進階指令選擇器 advanced-command-selector: false #是否讀取ResourcePackManager enable-resource: false #是否開啟海綿的吸水功能 absorb-water: false enchantment: #是否允許使用鐵砧 enable-anvil: true #是否允許使用附魔台 enable-enchanting-table: true #是否啟用計算附魔等級(計算書架數量),可能造成伺服器延遲 #如果不啟用本項, 附魔等級將在0-15間隨機選取 count-bookshelf: false redstone: ############################## #######是否開啟紅石系統####### ############################## #如果不改為true將無法使用紅石# ############################## enable: false #是否允許頻率脈沖 frequency-pulse: false #設定脈沖頻率, 預設: 1s pulse-frequency: 1 dserver: #多服統統一人數 enable: false #Query自動更新 query-auto-update: false #Query周期更新 query-tick-update: true #Motd最大人數,0為預設 motd-max-players: 0 #Query最大人數,0為預設 query-max-players: 0 #Motd顯示總人數 motd-all-players: false #Query顯示總人數 query-all-players: false #Motd顯示人數 motd-players: false #Query顯示人數 query-players: false #更新頻率,20 = 1秒 time: 40 #獲取失敗自動重試次數 retry-times: 3 #伺服器列表,用;隔開,例如 1.example.com:19132;2.example.com:19133 server-list: "" inventory: #如果無法使用鐵砧或附魔台請啟用本項. 將會對背包進行驗證. allow-cheats: false PNG  IHDR_sBIT|d IDATxɏy/}ό\**TIQ"[>࣠:nse4 ն"%MVXYľ/-1߷I lx~TfV }| ZY_|=~m~_jjZrJӒ#F#nh4zt:-quxx(dnDU,sEQYjrJ"EZ8J$uqqf z= ʶm5M+4U8Rr>3m6ARD"hr t]|>W4UPP2T6U6Uߗ뺚:99z|>5vT,$ILL㘟l6E\.yөdEuzzbxH$cYv|>/˲TT\.X,\.gvbr+u5 cުP(P((NLx\O<џq5Mmh4Z*m6I8+l6z>|M|8p8jfyz~_u]E"H$H$ǵ^u~~n+˲JT(_Jw5ooi8*=}Tɲ,b1e2syhXh+qYj2(N+HJ>TU:|>WRzVP|>W:VSVvUXzzbfQrzl6r4H$f#˲X,txxv+۶5dH$$ItZTJL\fS˥өɤJ P4d2bddtxxh VzVd2r9 ={LLFlVnWo٬,R"yZ _TG\:s]WLx\JE^OJVP\.5NUVf5dtqqh4H$BtxW:6c:fl6驊Ţfr٬@J FWnS\wWi4t~~Ve^v>i6)L*Rh:*NCx2ɨP(jVbQrYXTJrYrYbQDB\}Q  mke<ϓ8FnF p TJ Ѩa(44N؅eYfj4* @~_@e)VVj2#D癎kۭ^uqh8j6)X,j0T&Qz{=b1 CM&yr9ȶmfFTFcוH(Lq%I5M]\\nNTg*j:i4*\WXL+p0m:88d2Q,U\V"P6Q"uCl*KEcvF"fFrGjXh4ɲ,3~{{L&r|>j%۶qE"::>r]WXLNGF|d3ض|.'  ]}eһudYI27_nSffc׮ht=yRZUbA+b~\v]"Z.m%s %={F)~Z-5McsKLWFnY_*ʲr^Zj"DZeێݻ ꫯ*ji: z\UW"}_Ԍ| %َ~srYU*Ӂ.//MkcS*rr]G@VgDJxBAQ*s~~jrRbT*xwei^ѣGF:99$vET6#R\hcGX,N,h$l6;wh:/ Riv+- S,:??W24fS+(JZ.& q<+lU.^V<2 ҾW^QG}S3BL&\.VhTt`yժ5޸|Lfh4ғ'O^4eYժ)Ԅ>DB&;ÇJ$亮,Rmo^J[u]J%ٶm b6뵒ɤi6&q3޳\.zfGQ u^u:ݘi}_o3׉B|?7ݮC7X,Z&R$Qӑy_ED"r:::R<Cgٮ?2HI&1 D |M9vkG.37Ɠdpm X,r]WbQЌjt]U*{nj2xbv-uu=e2gntwUs o߿o?h4L|+1 4Nt}˗*+,KiZlFTjzw6=j0;<ݽ{WT-ohP\Ņ٬h4t]=\H󎮮4b`<?~D"ǏkC&#i>)H7T^rj}wI8;T6ۍ&ө6]@*V^WV*%m5Nw&M8s=}TDB^OPtX 6v+vwReT*|߯t:X\,dY.1Q:QVbPQ\h42c4hT׺d2 `YQ%IV+}6=bbP$ѽ{T({XՔf^8 ; ~_\.Jߡj~?ugO&Q Bѳg_t:OJb9H_B;0C9Bl6+q8өld2Q45\.'۶y:\U^W\V$Q7p@gggOǵZ7vނq3gOh<-JRjz뭷Ffƃ]_P0nWdRܽ{w7›Z?7??˩MVTJJR|ߗ$F0H--H29 hT777rfP뭷2Eu5ɷ̺0`0 KӊcJR @FCXfSLg4 f庞Ѩ'vn4)JBAXLrYPJ$t:D"ȴ-؍,Kl`=05eUVMKZU^W˗/MWGrxxNc2YWBAHV˄&Ŝ:al Ffi&{뵖˥ |뭷tqq/ ]jU@fSVr]W\Nyݮժ~)J^u#ϛΆph>dRA_>LW(4/Z(dպMYnv2JEX,`pa0X,t37~)f3I2mݽ^qZ-q5{Nd pe8ZS18BUv,K~>W"Ν;!5+(q9ٶn?HGqCl6+!?HoR l6Z._yft}}X,حpw[#?~B`$kCOd/ v+7w뵲٬? txx(udYd}_zw5L4NMɉy]///M$ 9\V:??j҃зz۬]Wr[nd4ʶm͡k\νG&öm bn fZrE[ @bQz]ob^yq]ܻg}ӝnk4ZVz4̨Gd_vchS8T.tL,.K >]\\z|-}gAVgggd2vJ$*K?t&t1 SչFc?UKy9N9p4 L;w4rө.//D^uttsS8<<4 ʇu]j\VP0[6$Tx\>46 ۶w( ±5=yݮ\ߦ[17WWZ.#lVNtu)dfTT`mL&Mj27dl6L&vB iWl oövT*dbf˥z̛s ;ɤkH&:::Wa IDAT2#~ֈ  z=5 BZv[HC%Int:j5އ]!DrYFl0 Cs urxL&Ǐi۶ W^^^jXljXRuU.W\~MGru}Jx`|{\t:ɤrg׾? cƾ\.#MS/xH&v<뚃\Pմg;$q Cf3kz 7)=g8j4}SKZVf(,^\ozⅮLG^'q4 Բ?tt~;jH$Zl&˲LWT;.麮|7c@S^W65ŁFa$$IF#J%(˶m=\%lVT23 wdMJg۶yljU,S9`l_X.M$E"}{믿C:::2e<|%{"\Ź나hu]}㏵\.WBY)<3B ˊ(6X,IӦ7 r9}ZV|>z6b=z$Iftqo .",PR)MSMCj203|le=XZX>R)]\\(LǚNwkK=ϓ$SXr8޽|znقQR1+Nu]0y2]NGmP(PXXv.l6ͪb|>hd1[_7$X^OZMDZI;PݍfIWPFQ4L&bhYF́q4)/^yEf0ZNt||,tzzjͦ oʥ)c @o񆮯foK* :99rTl6S65!#W_V\.k>k2VɶmU*f)۶80G3¼t:m,,l4\.M.b47bp#,Rޚ l)) C- S ;¿RU&eY:::R,  G± Ŷm.Z4 h4C]]])JT*fKɧ~D"!INf];Cth4X,D"aFGl٤?/~!D.'ϞFxm:DWVSՒ$b1cyy^u}}D"rlVzF cf&"<;MSI2] |hezRPp!HZ- eiG łr[ trr !4<ޚHPvXWbZJJ ___k6?ߟ m}#|W6Ur4$^{5m6ݹst8H$"i7r||l%6n&w!3KEQm5 #3utlC}TXѨOC#BO/dg|n޻6usszm-2iXL~_O<1zYtZ$퇇^*L"~8NMD"07Ǝ(k6j4}qtV+Z-UUj5 Y`7b.Tp+F da<<-ł~bP.ZvA?\X,LXax ׷V)͚|zH$"tdY㱊Ţ9$'Iphȗd Ы*o|CGGG:<<49JL $ h2TlVZ.fDX|^XL{zqُE} Ei%SIuݮۭ>3}S( Ȁ0$ٽ'.q2*Ĭ]=>>6ChTfSv[}UVb2?N s/¢Gx<6u]f3\BNfeYzbfIO>~xO _f2&w B#J/<܏cJ%j5ERNGiݭ. p84[a8:>>V2eYZ,Vkͦf 3#l6[2NNN[oExWRQ$1aha V ; 00P/ * 1,L&r]mnKN&?Nk8h73Y\6j&[ Ν;fv3TLv1 dYsn,Kmݻ&b:99ǏA:͚bݪX,h jUFCLfwnuP77BADB~WկfI.gb͍\wD].!\s{`hW4ʹnMhZK%S)mB/BfsyI$T(tzzjFxF lxru?WQh=z ?Yl_ju:7kr PT2k0-js\< HDn׬,JVZ,*J:99QZUPгgLz2yZ.8L̊08rJ9G9^NcnáNNN$ɤcoA@wk3J"FRUTtJt= C}'rj65MrǺ602D1y45i<kXxfp8܏^;qTAk6XHD*K rl^V.t:U<W*R<W<G}+dvmB8٬od2ޛ<9r׾ 3P*Į$%ۤ7E<P7?wWɛkye$Gy,Ӓm),[M6%6{9[~_lo#fWHT9|, Ya6DQK҄?f,?C c<|SRy{ضMl>ltrR! Cr"C|4 eDQD1 ZOVsEȲ__o7UU~yzُ'QD}Zɢ!QD>7֟_fpΝ;sUN5uONղ9NfQ0\FJ( Q @RJ s|ѯE*B:6){ea}z}||L0>qp](Bur: `Q ]! "lnv I2xA8p/Q!"8 2RPT@0a&%IZ?DQD677Q˖Ro5zF#IxbAQ PPՐN!2>$YRס_9nln D6a(T jE,Sef3 XxR@ௗGT vV \8 Ah yDQ8pzzAĨk \)lq]|+_p4u`^B^pibb~З:1<|>Ai9(:J2byp]KFp0(8==X,;ruwwZ'lۆrgl|zRyUt&Ǧav{ggg'/%J(QWhH(QϹe֭[;y;=~ُWiUml6o272NҲ,ĈHX*iIg?~l2Kϸ<ضvvMxR(, zNhP(Hu]uAEA0͐J4Md2UUK_ ɠjCUUNVx˲`& T mQDEQP(#|t a-*q+J%XE<ò,q AvQA4Ȳdr \4^\\@$looIr߇84OS`l6NjoV+TU-\:L6V a`60$$kM` Ԙf?ldqgg!Jh,rL(n^MP "U6Mz}߇y@$ \&a c2'L&-L&ZQLi6899:8==ERl6C6Zv0@(X0 VF j`NT'Yrx u1"GR!rDDZd2t2A.`08h4Tz~~qq w j`1 {aK4DY<ޯ~ѕ>=DM dZG?{ُ3QD,%JsnGlV,JN2,#3Vkʀ"-{r(TX ALf=`z:_V+R)tRArn`IQ!2 666ͮ >|˲f/^^0 46t2b Hd'ݢ(j! Cmܸqa1nݺV8qqqsA>EQPס:Z5"F 4aǨT*h4PU0 Zp4aYJ8b@*d2ǺZg Yh4``\5V0 qf0yt:rT*hښ}*:tݫP(4M(CBUUr (Pf 6 IJvwwk!xdEQZ]~Z.T*u ̡Q.1 0`.=zsb/l6qx -OX%(%$IbM8S IDAT'hv%/ܹhV +FK4,")qz7ÇW%J(e%D%jOA|`6 tfhy;_)Pi';G2B>$Ih(]k: D>ڵkNyo6={u!I]EZ%{.899,hZTɁQUUH0 R(BqzVXVDgߧCQCQF#|4A@P@h6!"&)|G6`0h }a>Ӓ5@A2' dYF\9%+MhyE9^TUŋ/0Liu,_G1 T ,cwga2 #<0"+0 eSB4T*jo[}T. EjUg㓿};QD!Y4$J(Rf+nܸ PV)n80c4  <+jg߭*Mut:dB'չ\N]A*6]7.|uض`rLMaggcePL0`6:d2ARh4avKz7@nW=:z766xE^f3 ƍ"}'"!(B.Q5 Th-Z(Ef3f3t:t AhX܄i,<iyz=CSCj`qN8n<h ߧ\..xl6,8\ p0ƍL&d2e,5MvvVuh4n! ¸A@ET*[2Gq޽Km#riR%JECD}TUy^S4EA@'6lX,BuilFé2ARp82l7o\oqr` 'L&VThZ82 NS_VeDPo]ȲL`@N۶!I`0k0`Z 666yJf*,` PTm6x26k`M Aq | #3膁8vX,KIߧ{'cj6, j(ttm7!!dYC^Lji¶O ]qM, |G`r|9(*:,{/0mpakk BbeQ(q!9:dYF!p!QTkQc{{$B,B:qzJY)8aY}|J];v&$IgϞQ='Ofa@X,4M4M-aRw(QD/Iɢ!QD>'vm?}_l۸ujEf~(i$IB*"xӀ}|ׯ4 zaVl"Gz!gy@z'!2& ʋH`qUUEt]GRzMYyq QE8&x<靝S"cyjj `6j=L&CuQ(JAh@$Lu-e,ަih4prr1Pf.qfe|b1vSq:F EbljI]iZX0W$I( P":r:[==ߦaujm EQQ(^$"%F8x{rҜf'ފ"ӂ@UUlmm`8*5 @p*B1: 25D0{jyHc;cxG@,>uKȺrÐbN Cc>ömT*0MlwgggGGGhZЗEk8>>ifHӨV܄eYu,yD0f:)2A 884|/KphD׾T*BgXP sQ(tz k,}Zl jd6a{{P~_~g8Ԯ@}t۾8;|ۥL9buC~7ͦǏb~(}MPVqqqNL&CnddBuaǗ Fڵk&_V#F@ՂyTgXua6"4M#;>ka6nr(f1.Bgsy%F۲ѻ\*R <0B <\E(aY6&@f 2m8nwVCd !<>& z* EE9-D3-X~~ww,b6 a0Ȁ, $ SCd2{ c]e82 2 r mvl,4 FlaB̉l J\lR_b:AO|k_Ó'O|5X9<50 zGQT*۷oZA@7tp4 iܤʖ^e`peg,' CQX.5jUض0 +p\ Y>bx ۶Nsfv{_Vkq7kR)j:&u%J+Y4$J(@B/eY>t]( Yl9r ۶ R 0aŋW_կ'AT*^GߎP [Em(bZ0 kO~EX8F6B.t:%g {MRŲ,F#j, ~dYdYQ՗K\.bQR'w?~GӮݸE o뺎FfׯtnCFeYF&ip|7xz/^X.yX5ׯWxg6'u%J+Y4$J(Kl6iQZEщ'[dYFEPUj0iA HRteRyY^!P|ptEQV=(< a @P ?yz0L{{{uP*Pב+T4 j@Cf&vVI%aؐ-I6Xi, AHӐelJKjE5C` %X.8>><Nib2@Q%IiR 2|x8NS ؏U52ء8::"&cTr9]Z>JĀ(ē8??ǭpM <|ɓ'h8V ېeaXdYj?\.c\}rŘ`XEXM"ܹ(\j߼y>v]*U~N&+c>]G! ߁5;==ŋtV+ZdqaXTߛ'/*$J(YJ %J&c.yt::81ibtJ l؊Nyyg 5dRf4Ze7W=??w{szڵ71`yy"rI8ƽymA7K92-t ( ' ͢R@QdH۶E @_dJ81Qtzk]M3;jXQ0ARXV( $ ibooe$ӽTUE6 0M@o|>ߟknu4D% ,%J%|X(2b `{{`}$Qh}|+_!;;;=j4~y۶qppQꫯbnZ`2c8è+;t:d2mۈ㘜QAeLS1dYOݳi1l:F#( Qz6;xאpl ,%6; lup]~$K6D9a>#|1|]_BחI" {TQncpt:Fͭ$YR)+xLVuz> ).BQEK#q0 %U"+tP! J [-g3H}DA֎>lLf]gEL'Yir9, J%rOye8t΀Z a nܸrhQy`r2}g\E |񋯿EONC 0L.G)̦sr`&J2%RG^.\o z!NC_.G RYխ͍lH.%J3RhH(QnWUeF4 ^jQF8ǭV+:Eg;؀A*BXc dxppEQJ AȪ(>|={v7eF4DZE<{m!"ٌea4aww?F*a?>+`@|Fm<{ q){>/NNN`Y9)% à>]<ݻG0MK}Iml$I666sq-:cgg?[=`0{ `6" CDQ۶!+kgM\F&EIFĵ`pRApmZ-Id.˚GEQ0i:e!}UUu]2jfbooϟ?QVC6œ({{{@чl6zN aPjB\yD*ݏ6-0X;aBuij(J.J778wRk2 IDATdB T*EN8ZmE(T\I,^TT`&X,d 0NQ, Ӳ _h{%~%J(UWhH(Q{_ڟVɠ^CUUm8\.Q)`&h0P( !b8!q& 0Q|>׮ (׿ߛLfoF#u0`>@5L>fx ZJ84AE(A@IMq]$kĜ('''t- |0j(?ϡ:&Kl&g>q`c%-Lzaɓ'4ȲE\ISj4 N>)JZ&]Z 0 bA44HvMU qZ%c@:A<|JjX.NeT* a<c0 "8`P!Y.XVur[[[fv*gUX,RE'k( <\.Vv e3!ˡX,?W x;m μ1XueYԦ#AhZm F]aR,{h4NcZu]} C$ӽ?8$u%J(Y4$J(KЃL&/^Pml>G6%Za˲PV1!2ZdBB_wvvp~~\.GM7 e}fwj6O (g' ~_|~Ȇt:L&Clq`;&4mpGmR`<#"gYpNwg?TUEZ,˟2XeY <z}ACrEXVZjEQDFEeb4˸,~-OO$J%bKpfI- bf~iB$c XQiKLS(BeX$wx< F`ibgg \ DTUj5s6Z 7Ur\ض Mg\v l`jJ-incX`oo"G!ǁ(/IE "%  qTqY,)b"3x9އԜ |?\)^8F3cȲLzR!iVl6nT*HRlb\.B@\ ۶aY677i^.x8 vOҒG{r實]&J(ECDk<999yX,D3z$ A_,FC޽{1NT i"<nqS%5ʊsQR`ZQUi'`b:FP@&VV3zvvt I`6 MʒZJB6pduL1٢ul5裏E[uXXjV au(2-Ke~(!EVd<[Ect]dY, |=k\σZ*A~9[u3fe8鄿8r՜ai+h5[FxQAXC&AW+<}vL>< <#NjaZa0@Q EQ0L \. $Y]#۶qttr41 ptt|B@HqZ.!I,6F]`GTBR!r5 [>>FZ1`2,!>wRTbov9d4e1#@T*&w rUU8T*(UURVa8¶meV+ Gmrڰ{͛X,4 atwv'/%J(m%D%կ|< t]b@.[̗CqE8B2ld*2o߾\.=l6t: ˲P.E (V={]\Z,&1!WTUey10x<qy`l8̦d:[FRlF^(@tVY|~ ` ǚ2Y, "ɖ Ek#k(Jx r)(h4v$ш#x뭷ӟ`ggx9:Ft]'X`.ƊFDgggi/¶mj!J0 R<{ 0 ߇i Rw #-X t:{n<c>Kh\BelooAN<<{ me`0 'iXVV0  Ǩjd`ѥv}2QD,%J3ԃ-C71M g?EQP( ;j5y8džN41 j5f-{{PUIzZ/|gggc큢~nƕE^\NzSuX,b\BEj5z=Z q( "l&Wi ưT*}꫘fP@EEb8ƅt7o0 lyE41(GGG<a38r9V^#QC-)V\.lt: i/8rDZ!%u{  S5x<` Hw`üm8==jCxAP*0N> џo620ĵkpV+t]<~mc64MZ ~[r?<ɖ |9͢h\ij (CC0s~[Z.$ 3(Jxx``APV1aAT?eS֔X,uivr7lnnRj5bXOVv JBZ '''etz =x~]IN>-qt"8ΨT*!"x {,φc6Uuӧ7xzzjܿҷAN!4ljh8;;#Wk`1)eQ],ʖ11KR) v!X,g>@ ܔj j*jff2ϞGu"E+4D)oY#wyX$H;R) CȲL[RYQ`&t]'_ۅ d9Vt8f0tx zT=W*Q_$$ m8qh\.xꫯ>2HNsc@njU}|aXl?{}q-`. zL&@ɓ'jTo&J e!C4 j;` 8>>`$d>6xZA$Ķ k:`zն## 4 nVݻ6Ã~OdVEd2IUU12LDE@PUA,hF~=&xHUb&FjM^>'?|橦k'\.GzVX,(̍#22 *& |ߧ[<'WbeI 涺t'2 =\.c4aHa|NpT]שrs߅ڗ^{O>yr;}#E0EH"E-TȿOċd2T*iqAgϞQtm _~e4 8êȉiB(~Z8躎}kCduA}ݻ8::"fjFe1Jp81FuYusΝlH$iZ߿vMYeA6 <(BhNC@DtZ0~l\ `y\%p'XǀU!IdYqFpsl 1 <%R&t vx9EnDQD6^|^e!H,m4Ð"=8;;>m9C&,˔w].@$ y<{gła ˡn*fcEA^ m_( o(c&cIr \__u0Ru]Z-r,ud2&2 Jqiس1͐%.b.żsΝ;!Ny݃qb69Xi\"SUpT~˨2RH@EH"E-/{l[^pH}c4Mj`vMuL&rpA4뉢H^q||d[nQ rD׃4` `:+@Qhz=ȲL/cVwrYM_^^z~{bhm۶8FYiCUU, TlX bj(_z%:ڶMuJ@UU\\\`ooöm 3 " z}ERPS2;' jF8< Xwܡs.Ax אe ,֭[I``fu x)5F8>z4EA"@ڴvl=?!J"@UUpszly$IgeS6cg<w^c?~]F)h)RH%I>`(bh6( 0 lr,n4={FM0>JVE[|>8A IDATqm+f( <σeYl6t:E!}:T*QC&snQ^;E+zͿW(zE~ɧ0l~4"@^u]y8]jhʘT(n5Nt:8>>FߧH } X,h N D#PF>'d22 =CP0:efigxn{...iA#mˢsAh~$Y,bz=syNO.)5|/2f9غ TU fXebۅadi@}<IU"LbdǐRS{b8x$YR fvvvH$jnljl6ٌ q!1Faz&WTz=@у§)ZemHxrXH |߃,ɈmAHQըm&|jB|fO(G7g 4Uà`dqawb0"I0NZ()FZ`GzB<nRuke4 c\b8BQ0։8Gș&<^' x#|"dqN> àDf ۂ5MNUM}[{O|!o}Geu] ujR L? |CA,qbCQd8n=|D<ΰZ- 4Il4 IvU<|PGSU ^Rb1y4M q<޷ugщrI`PI$IbPtQeH"} "E[;S}| dYTg&EA&A'vBz=}a²,z=躎zHusBacu^.lBbll,̬6s^#Nc:bZy hcF pB>>tJi?1FCYV>{۱g,  djK3FC2D>u$z`28??'qvI$6m " d`Z-r0Άitm881vww{j5\^^L&M$ BVcJbAy\L&qLaشK˲ZR6o;'gy22 4MqyAuS}eV&Vf.$Vɓ1Hӻ .~=v]ib2`^oHkzMUU>>..QV`KӃӐyjY:hTtP, G5M `Y<σyN/5} n<ߣ#3 2RH~CEH"E-( ۭV V RVU2ǁ H&ߧjE0N6D";w,L&W_p8*7qȲ ۶ŰZ fKmcBӴV|Ajv mǰ,3i%`a^@W@-2-u]kL&JtuZPV.,`01M ,z!I1 ($ b1R):DZXoXq0@ubz=Lboo\~R,ƓH$X=`5(eUa4 i'aՠl I ?q,Kp yARLM 2X(JK{(I^/O{ATw)RHACH"}:::zgYsBQw xT,SO#NSz.t:%p8j?Þ 6h4 iB4qiznSAT#\:E)  "EZ-/IrѲ,k C\"_d0>fg΁BFpyyX,L&dI4<>"&!\, E7 >ۮqHdBD}V5ȶ vqqALe<ٴm,׀Rpnwn (BUn@et]Azr i<tziY,q1 9Ã#xR #5:ppgs4-M:j5[ReM!@$JğX,L&Ð u]bEVi0mYح"mh5ܙ Q K28X ߇mPdzvM̅ٶc6A4\__c\b6q(BVi5}v<GzZT*ENB@_ +* >}=\=8߼xݛ?ﯯor9T*\^^R(`|>O|bA InL&^NTJj(J8xj!17t4ɼ, Y}z>_<y5")߿p}}ztx<\.Gi #ea!H@uXEߏ u<o0$7 jRbdqgòFbH_WƑH$hn0wgя\__ömm]LS8x0 e??1ñS㭷xQEr.T*UU*&qXdYF\ӧOX,04NɁ/ZJ3! 9]œ'OflX,֭[h6jHsdNƧa.x~C]7>:o;c<8cA\.X,b<Z  HqJJvNV õ,{j 25|GXeYIpJ'''Sx뭷(~ "* R<ϣmh4( 9XV!{au3yq}mömj` TAh]T K4gj d{ǶY8<ܙK| b`-R łV'7e w=M$_^>G<ϣ:Idd2d2xX,t:M'2c\pzNqgϞ#͒8$Id2Z nUNsV)gϞP(Չb>/fggqسÜy(x EQȱ{9u_zn)RL "E?],8;(h45EW[,h4`05F0ٶMma at| ZAц&/Kmr|i yAbl6meYdfGu^җ^/n (r0\׹\xm4aEXk;2$I$"0L&*z4M(Hrl6 q`L&D"A_h4B,51dYah3 hL&!". A$ ll;].iý\.aC,`բ :8X@TBl, v C灔AІ[Q|}|vv~cYX,' dY&Ob]^d ˲prrgϞœd 7 łXR T f!Oݺu DGGGwYͨmTʾ'PU*$t]V X.bPžnrIV̛;R0.#EP4h)R/@-M..p}}JxL[=|dh6lD}iyr9 MekVy4MC ^+v]j'`cudш2%:\ץm=#xt:a<r\޹1H˲Vb1%ۃ@\.jL&T*׾ p6lf>YӀ8c\Ra0aQ dx~yMӡ( %rCez=j8ahDߓ?`0f)J48&ݥ{m6mӖ]$Ķl(¾sb6!\dƃXf&{>C4}H>=yY=1& Rqӿ1N0\.{tRƖ_!,8]Sa5Jlb:J2B8c};dL@e,Mk h40 P#ꊢl"8P(@# Ccz_e@u6\.S<'SM-0Ш$IS7F)RACH"}$m-cWl?񏱷X,f 4@ΨWWWtHn? mi}stPi#.U Cs\.0 [^7o<نuֳ}mu\.888(]!2 (8??EC(J5۶K3gd/NO_m[o PT*򗿌[n!qE>S if, <@"@.4rtcM뛁ǡBQRmXPR* 8*  4M {9%@hp-ǎu]sz>|'w([n!Jja2T*AQ@BҿX,6Y1.R68H Yngέk^___|wzSz;5M>9؀`0t:am$AEضM;;;  k, a00 r9fd2/ `0^(d--5MC>5xGc(YSLz¶m:.#EWT4h)RPGGGbyGѠHV'2X9Jm%Iun\R`Fț&,<'g ll>FyW_/KF#Aehx u`KX,}\__Ӡm_yb!$֒.%z$`X_Gb18::1gϞYRuG~m@0@2sF#}LSkK60 !A& ҙ̦-`6kX,6 'X ( ]> J(Enc2`8R;t0\V( 0MBmE<Aip(< *zDIIUUQTte0   q;@mQi$Ij$x9EHIy{}@|vH C|򗿼1CZfJ=NPg4lۦk[X,L&\.1qxxbT*Eudv]$IA~٤{=ꊞT*E.V}L$O>k%a9siF#  6<]v}}Mm)l@5bRDC0 &<]ס:1۷o#PlX,u'jagg-\Ҧ*%lۦ2iLnA|8T*N+ dvvfNfVL&;oɓ'7ƾKw?|nQTTU˚ʿf3j`P;Ii1UUf6d-/e nTndQ%mrxߤRN՝N]F)үh)RHNO_~4fmٖf&M, Ӈ|ER|>[F8 PBjzJ/~_ ^jQdXfi/Pۗ*:Vft]i,*]NO IDATX}g:F*Bߧ-4?O&}r9_A@ ]>կҦP( Cp8x_}?1z~=ϻ3oupR)TU([?q( xh4pvvZnKORL0 >90NQT! ChNxZprrBKݳaH-ϟ_(78 {qx}}M-3eAQ:3.jBX$P<.}EnSkXjQ|>'GʎjH"E*4D)үG,ɓ'"i0!xӁ$ItfLϚLDz vݦs躎 p~~" /2. @>0NA`6&t:lF ,7G:FR/X-m m})wjeF>>zI6ά&ReLSy Cat:Mm <# ÍqȚ_4=(BE|>'aF%ݻMMX,p]\\\@,2̑b(b2=au& Qqvvx<=bNmI&cd2}B1`0 OUU#"Oī˶lH"E(4D)үӚ $gzMՄ6ymD N#ٳgp$u]a^G[d/Lxn߾D' kod2Nw9OR0 dgb牫=4et_k6F d21}.} Lٳg-DJ|\.˶Fu2@S5bNgEeCuX7ңGߍVXl戭V m#`UQ,K|gV4prI 0 mf1M:t:mTUqtϲT*Mӈ I 5*i2Hla<ϣX,b0`^8z=L&rR0,YY10aĖ`0 !I0\O>}zclb􁦩oA`zS` Q)z;;;puuL6M7/q.XCL)2 ...015l6qE|߇T5y xX,Z `88j;0 kC&gYۆnhD\˲yl=Xdr0 uضd2` cBu(h6G?yrC7pi:~cf8,cf `8@SU .u>q `(dI}c$m< @Ra2nP({2[P,6Ķutz p܆Wa|{{>EUU#m)Jb!1^E@O`L $x|2 IQjBDb L&mQD&gqXj-7JeaXv2RH "E+=J$?o۰lՊ~p<S~8b0K_YNa&?& \]]b i`F)BŽl>z66x>aq|hZĎ`au7zY,h4BV LDل(Ը~ ?kF_]`UleYPT^VbN7o (] QmV?qX @-"nݺjO? NC LSdB)eft(BjE $A$V 4tM#_{=%W^p8ӧO7EӤzN,:>F1͈OGJMشG$ ĸ1@t|>|vXM&( h4.UXj6 6bcdYr<ugMa*$A1:lۦAJ( AeL&:kb|+:"lvXԂŅxz^6l4{ xש!bgz\.GqPvxW^y"j QlkZmsC #`0]"EYѠ!RH~Ee/.K,W+30 mt ]ױ^pWQpGC=`i"ͬ~),0DqPUB{PeYh4zrt6t:H$mW|GGP@^'CeD'''mk`m4M, =KDlxl R(8w?~H,;>>ܷKGיރ,CeV+<b:(JE\8 & j Fnx(Z->t]`8A<gy8>>v?bYqyy Mprrjf AP{=ض EQ Ml6Nge-c2 6 C|(X,ǨT*r6GN')RACH" zD"96A0D\&"L2c1dYڦibHu.}MPVA4zNyVEvb8rT*{.bj @D,D2F#Ip}}M!3I0 q&}0}a8ca樰m*$I"eeA>;r>Ng7&{__B96bP(DO&&>6 0DR ~P 9i^VtRUVk;qբ( fG%DQD6L&x[.z& kHR0 z~X a`wwɄ@%(`dggGq^c\BH fwEAl6~toc6kt:HR/<ٳiL~g4|eVyš9%&)=abZA?[ś}31$5B`vM1jz8wE%8]9:ζaŅ/ t.//Gnlp%IZ5g/ɠn 0}}}]F)@Ѡ!RHj8?@ 8C.%8CFVM*PnA 4M8$ky[7xшnOX,R8BEN]Va`oܒHM*]\.G A0 "<σeYpd}???lbh6(@UoΝ;T*RNmd'.m$G~yޘӣGLcP]ah4Jo_>yFD)nEH"E_}O9{P(`6QqaBUUʹt:M|`\e tJUU(bt@4M&V.NyNCH$g}.?`AxG+YY~Z-+-` .F7 \!-^;{ey]K+vc%uUe3oF `|2-\A}w:woda$Go!lfQe Y N |D"H&{.DI8@q)혦+R)躎;wa<4MAgϛpȋ₫1Nc^"(JQױ\.WU' {:A8==E2,LhLH{.,>r<jRncX00s4q]gC,;Tl~_PӧO/eY~Xvu\b{{Z B|pVMF#\]]a{{eVaZqov EQK-<( 8A`DL&Bض Ml61NReuH0f٭&Ly0LBu\]^" 14_ٳg7 wQ"ٌpn믿O""1 p]tWò,v<ИZǙH$b|>׆,+ VFŐJ0[7P/"WQr9Z-iFנy,!kqҲ,0\.!I(G$>0^Nl6.bi&CM|uL0d!` bn>K[Apnp<щ\PŎX ZAn|Hnr 00XhF\Av\.q^ eY,{(888a](h9!e]E69\0 CR)|gl秌0 ÷ɯ\4kX.L_V '$2<-*ϟ?(޻w֍E?cW1 .HAe" i"N#sR"<5MUkD&p8yV'َ00o8E/Nsb(lWpL?e!H`41 p2HnXyp81t\h4X4`&Q~Z#JЗhuN\ǝ~~R)kq(j2E4vYekKpb6Nbxe40<-+ J(af:_4cQu㽽d2|qTW"Hp夢( LSc,+IMD(( (MRBh)JDQhFcv˕da'!4El6J°?9p'i20 !AUr?/oyOvc۶1>uss(H$ Lb[ IDAT. ˻ヒ3Hu;/>5eIޑu)vF9"C'bPc:b2@Efضl5>ADbi<sa:²W/W^{1e4aoo\,2NNa&s (RT9K-&x\觺_^QL0kM 4L0F2$ɒeY(|Z[P*VBܤ0N@~ϵnH$y={}a߳g9N#޽{h6,Xht:^Q!i@'Ԫ]DQd^o6a0^QT O V(_ @4-G0 2CUEZ-b1{܄8nd2흝nA?;;HD}gdggn\/XT %a \G\|<g A蔝;o߆eYx9/bX PT|wwH$& dYFĝ;wXxFh4rPUw|>Njb.'}cģ\Vd2 V( \c.ZZ\E*B׃i|'›ot YQ*P nɺ)& qjP=c`9:ST@[w)2QD`Bv@d• Bx A5ĵE8\J&솠 u!"vvv8666L&Sd2Z]L04L02?|`15&8Q]E,C\m^f:$TU,H&|\!WqGRw}ۅix뭷WyZ2&SɆ~}} ͦd2j/^0t@xH/$aX@E;wA'd$A^DE់N9iϩ5$Iu|N-5,3؎>~'x~|>B˲4olߪp(rO? ezTUEg@#E/..xreq{rlۧrpEQ0!24/$"HnRIՇBENv0/!DtjX<b<s [V8::$I*e2rI1f}Y*om[뺎\.lNçn'tL" &B*P&/$A\.ѠJOI`0aǷme!qylZ ܹ7|NP~]? 岿>Q^"n:H&'?b0M9 d2jD"/IRPxł?y p$"JXd#ta0`{{^t]ɄFejb75h[n@NQ*p}}̀YACu lmms! a#"2&m4Xۿ9FB2qalI{*B.ctjCF!",R1uTwwl}- cXI38.Ax`0`0`0-gՊ.h:n1 P,,C$4 b1$ 1xcv,i1|lb{Iv(eȅmۘL& N$~^|۷ouiZAEE, $!N3lt:7 $P%R < cĀ1 dM\{{{!(8<!{7E TUo,v/tz9TQ*E{ah44\o6&(<|> (˰mDk٨MLC$JW ,#I#e1"Zyo@˲0pttħ>B>{rcDrT$YE C( UU!Œ~-֙Lr4$$f3yL&J.>,Çma@UUn!6d2A*p8֐BH$M؉ex lmm<'=::jLMlmmq6T_it]G*hHT O׸w2 :G@X*L&d`Y #8=EQDXdA%3u]u̗~!` L:y?!:~&a7h E0>z=ȲhlUU*_Nz%&0LoEQu]|*::EL b{QsT)Y*xI_Ld6uxvPIQ1N#8, JF÷=NX,h{iPٶ~t:bwJXX\RG"7RGnl>0M㟝{o?Og3N|t:(J( s:<p&\;N^{5\]]HF *d2L&RJ "4~_h@e^rD& Ir>m!@>?I&8|r1pG$q5v qkiBucL'e1&u(b$IBf&J݆뺘Nw4Mc~mBF6 Kz=E(|>G6œ'O rכHUJH$]Yl|wX p8d0&]__!J%|H&~0fBS jb6ݻg3Hz~o}-L0[N 4L0y>L&hYbZ!rE<0 `(/Z  wbw$Ea\תeǑdd h4H$FD"wv˲ }j]\.( DQDlۆvbR)hUȃ( }H=F#aDQkx6iaq$@epMV+uv;qg&aVU|m)* !yhmi˟^]VENeaj (G*f\ֵ1U&5\.!Ap?Ƌ/(_f֣O(W:N6aXU1L`#ሀVa!@TBb*˥HqlR |~ݸ`qlD4- Q_'w(zEq`LΣ@];Vҩ^w='2*26*񱎆M&LSB! J%yh4NOO1V:?o 5d2ŋgVff<B.$86 :n߾̌hhA!CV+a . &/BC0Ko~hBQHo%PU|HVcqf]qBh{j#zɵ ?y}_dYF^iPU;;; FK,Z}Wy|y.//."DeNF/P03XRUU<~o&b+y2"j2 WK}j|.I,BZEX5L֭[|ӁjJ}a&~\z?/$R+Y?LmۨT* lo0 L&(_~hwd2A>g@b>GP|J} , (|N&$|>G>jp$"]^eB41 VCxP n|vrz\r0M 0MR O<?((A9A˚wl$A$ܿw܁eYF1[Ν;r|1c>3C$G\fƊŋesݝN pTBe zulggg<GGG'V H锗0ؙL'1e%@Ki\/ ClT/^@4$coDQ17#bX cww14q^O&(>& -X}ݏm{F@},GfYi{{CH! |>o=kT*lhq-z=z=<|4 f1F/L`dmB pjz7F999I˲aȅB^95"PE,r/TH>3)XqU,P(p ۶tZ! qPI&a󸺺(Bt:Bn9q:4]DPY QבNY0Ma`Zq|Rd2 p{Xqt@u&a9?C_i}777n!"FDQd>?(vĶm9J*v' &` L0jU#h'Z,Ћp8,ˈbN=RB/Ew0O3 fG)j `BF<ݻw;p=-PelBuLSN]p:u1Q*|s~HnC?i?e9z=<|agg-20Juf|r>`6;JEb`j~&A9b4n!1<)^.qxxFp8̼ r}W"U5f}clfoܧ9 mY8hR^FNNZ ũ^-cH"$H]__#aoo\EX,F q"?p$}R*ע`89GNqrr z9 ΕbYT'5=?"HcK$@ Ejl6\9:3/R%`8H$(D"41X` |_uMIF,xZB=aDd28(=(A,۶YC@Re}{٩( .<`UU1 ͡J` IDATXr3x ۶u̗n!` ׿1u^XɤbGL&H  |&"b* zJ0/l 0a8-"C`Ҋl^Za6^G)=z=n SbddhgϞ#h;;;@^Siy"K1I.j`I|1L /+Q `Ry' e!)nrzzJvR:8~yK~S~߽FI* >}4aX HuH+S( )W.}28CN}h@|7~;1uP( LLp ^V Q*=Ex<΋*5f+8{d2\)h4y1_:y'~I$x2k2䄠jGEQyvwwI@x<΢ƭ[pyyɑ*_f Z|1F \"...`Y/FF"CQضv͌ j_8;;cCEP`ˎ qGGGFh6mTUO~}|` K7L0|ѣ0fH$%t:~H$D"+ q>X,"͢b:"bss{{{x9TU9"8G./to&Z=ooo t:*r` r b11Z6硉_0L* ,K MӐJ ~򓟠P(28ޱX? b1Ϟ3ѯK`Xl69YtDQO8-`\n3Cef3R)Ya&ܹVϟ^NPU|Zoiwo_LӔmF,״F#fcΞ@}TH'$B, fAĮZg 999Icv>~DjrD"\N!I3R j,m d]_$L't:(Fy YrVjYN"FB& jqZte!b:X,Dm }9FŐJ0N3 L0|goo%yr98UU_@a0G D)=%@6^8;;CT>_L&\]]C{0۷!I4MFvwwŞӑHfTUE&xaYMS4qF#RHKj&gyf3J%Ot&qYDppI d`6TUE>S˲7@5sB3gQTJpyy vcy$Ie\XWVQ!yo [Dz`{{QKL9$PS@(B\fƈt:̆a'INP(tT*jMw//k7 x#I&HS&˱pG컮ˎ\.H$GL4[->_,,$0,nD"|Od2u[H&SD"|_J%X]בd8.T(]>E0uR,i"7cEh6DVcx-5r~;8,"tʲ3@QvYFCn! wE(BPQ 1RY7$37ш]bB$*bٺ=0?sYj` K5L0|p#EQ>#7t \X uVj$IbA#PV_èj\3Š:rW,կ~Meﳛ,r9lmms jd&(2"%UUeb&xM Mbb@!|~睷0?D"2YV >|SUURe pן]ٌ 䂰mb(E&+ J.~%9#h4b7 %bxn =ڶ`FT*B% qY,,RNQYV<D|J[Rnݺx<QaY&* $IB:fQR D^hZ0 7b4!Lns4iQҗX^Tpqqxp` K3L0|iѣGiE<eD"lw&;}^EɰmDZEa4*)c+b7|LJd2|bOumA$l(”]mېe,AAi|J|>'Pj0 EŤ:oS)8T*.,g2>"Lb8T*q *Ex鍉tuӃe,X,k#|ARͫ8HX2 ;?MȟdOB#frTUgCq|>D"L&UZf!_!O f躮V( av|j6H$|F<_eW,:hY7hRϟ#PDrheYn6NՏ$a~vaD"d2>e.mF߇eYd'! 4H$p}}͋2nTކa|NOr߻(\whkp,Ӓ$ɓ'x9={'D4+(4 r5A,9U& ȲNZ։!!2JEO_{=r9f!ضGEv(?bַ^z|[w}#8lX,Ǝrt]zPFɻeYB `:E[y\_nQIwOo (ŋDe^. }*B(j/^peY=-#Lb:=⋧7 Yjj*`8"ϣsCE\~gB'J:JkH`,sKT8$I\'Jq UUQ*f\b>yIv6Z#<؎uYpd24 ,6YV8??7.E C|+_AEDWM0!",C Z.u\ŋeELSWx=TUjaiH&fF<gA!X,P.f*62 * G0H$Dַ0 f3dYB4("gr, &` ̗r>|P犟T*KZX,H$hMf$AB:&! t]_|X,VŋE.={X,ƙc C^=Hdǐ$ rk9!Iwȓll+c4Drڃa ^կ~OeeYΗF#\\\pԂrR ZFLwT1Q4oͺnT*ɓ'hZpR Ϟ=C:  Gu`b1R|pJ& B,B4M/De,w>}ɍE^^^@+˲>X,r(­[X,tl6y F|B9>Qׇ/o+1-n[[[ BL&(`GA$aQj%IB&j F۶ӦncX`:bggT Fa^j`J \:OJ`(ƔOqq_, ۷a&ěr q $XZ[z- C(rEa`oq܄(B888`@RiVL&l{MCf2lmmAuaeR)&{jh4|~B'p(]*jpaz|c`YVIu%%!* NX.1_>{dYӜ4oBEf \.*9( eX,q&wBD",R+ ]S~& Xv.K5ֈh4Yr8t]v,$IfAQDQIZ_?8( vUa<}m#zX Ij'T罾F,cDP7Ar`0@*) T l##srD^."xr~q]L0O 4L0_B0H&1 `&t]8|F*7).//9@'DۧXDfFY}~A<TX*X,PV1R)?G63o/ف,H$h6LB۶Ha>msƚ@ `\sɄ3lHmlO I41 d)w3r2E^-Ó[b2`>CEfEOEF"p^EA]+ʡP۷nݪ޽{ӧOވlvG#wvC=l4'|"uE5#Q>||cĕ~t2XS0d]yt:bvT*h6wsm%B5=˲ 2& `-Ժ8Ap8>hi'b8+COB$UgR,j:8w֧X, ej5i~,|k_C$a. [4/Kl]0[[[X,9֒,krD_%"I?EA8  Lr*]\\ Jq8 aF`6 }nnX,4M$h8y{Ƿ?}q~~{L0N 4L0_Ѵ'ɤt}k X.hZ4!Z j>sdYb1?6 18Ҕs_V0M 8G)N[躎hęp8 `Kkf3/tCeq#Fbr!HI uki >q^cccڥZpxx%nFyIZƵܴyBH!W >2 :78sǾ}νO_ߘA\NԶAHlݻ| U*ƽ^{W߽IwOcX#҂8ƀ"'w]O677P.t֐Lۏ~rA ,5tptt"'Iv)Yk}߽q]J]uO ޻:`gDRDR^>ƽ722#"2ql.În`̝{vxӞ f5YY;+23^}~DJΩ`0ƴ+x#*E2xWѷa#]eapNi(@0s{n&8rl4dHJRkf308::b>>s >LD&x<~|>g܆qa;[!Og\R Nc0pJ[hEQva67VG@Rf;w0>9ᖲ]E6eeY /M( I9oeYF".A`zF&$xbt}"JuNK9: {:=/ΦXWX׈qq$ T*dYy\\\ Lb^sd~#f3ضCi۞|\[ +S!;zԤS4%eo# ""oG$Ib:m9oH^DXxxȉYGC "Z0L"Aqtx~D'v*'iI@4$.R5R ǘ笸x2 : dY ZVM>LīW*NNNC<4PCHda zFRRhqmZfHӨV,]4l{bömL&pEϡƌ-SdHfÛK"Sr JAXxTʕNaL&RW4SU?V߿TcHǕJ_5`{{{PvJ: *gY=eSd2Fh@E<{L"2 B0 , ˲P.1qzzBrۛ[V"kFQB0Pzp]z;2NZ m9~zcQ¸˰ 띬pVXaF?ʲ|LοxS^v7 ߿f`h4ʾ`ڲSBRfrsD"e2 c0pzFP`QiHAR]ʒoXrLI{Ƈx 䩦7RЦrCu Jn6N899 0pss2rO>ш;;; 12 3^rZlݻw!I8qjPs#^*P,xf/^`Zq)|$O4|>,OZ-Eb1}96 4MCTz'u_&zV-iӌ<\,IBDrcl6:GN&j5nw¸˰ 띫pVXa?}pda4iR@8v:+`Yl6a,F~><| ]\,4=48<σeYF,cj (4ṟzpHR Z TUE |y T[,9ه^H$P(8lq>kOOj0 4Mlv4I8F3Au>ti0Cd2cVI"'V; +ީ  a;]>IL4\^^^E\^^cٌxg iEV#. nF QЁ@?(JA@aGr0Z1Nqppl}zlp^9΍#__4MyNx AZ(J޽{GGG/^}V2F#")Pٓ$ |*(A&7AŠz.J\. fl".ooo*&dłbJ7I%f4{( |co@)H6|JV"S"vR8>bdiHf;#6 _EHhkE}t4 Z;m~T;(ݻ8==eEP" I GY9:w>si,\mHggg(<|IqX4 F"Cc0[ C;yl_qa;S!zg~dM8I$I2h8@MC\f5mi&Iog",bÇy6Ϸ4@M\.pOr^+3 xssÍSXi<װm, C<~gN8lrD/K9ÇyJ񛻻,c4 L}ϙLR /^zx!/<7{$&iNe \\U&D͏b!8Kf3z0R6F1lZQR'2 QBWWWlᢘYUk4\.(4 v+EQi۔et]VHPz76 ma EzN}J%L&VIăϺ*ynF MӐu "J1BE{q]S>.%,jDL!!'P*p{s`Y-EflBj׭4`s^#ٳg, ~;;;5ie$quu]VXD 띬FX6xj+Q`Zq! ܟPM #.h܄Jt:d2d2bz]jtTypsK^A(IC ۶&шT*n p||$٦ St!۶tx!a?DzN7MAct8Xl6bzj3 GElkr9N3_%[m@,6 <8pDeY j㸼-4M(sGu]3:ⲌL#뜂qppUUy!a[8nmDQeYqrDNd2AT8E< HX,߲,iL`d2r@YqA P EϚ UU1rp_F&a:20͆)ȫLQڋłľ3Xŋrd2f|}@Px 1&agwfN1Nl6h4 ]qAStMzt: Ql6L&Py(X,0MF6olY#˴jV0pqq[d @>LRUpe:޽{r uakw^u%}&exZAP:&XxTEx<%Q{ D_k4z.gm{4eYaDQDT5Z[x^oicNjf۶:LD\F:fa,MdW/ @}s{{~Cd2߫뜨@\ hСBl IDATj8/1\E\FE$i߇8}P |`8rN^?P KP@d0Vj4/^'>i"qdYEQV"=+zV=u! Us,tji;5-HCEpi%H*z{? iD. ض҃;&UTXUn9Rk FVp8d@dX ']VXou 띪ILQTU@DPolH~81"zMvQ%C?mL H$A ,+[TU%ѱX C8I}}} QypA ķ~=Ȳ m(?Ϣkx4,#;IIi^o%,8:7&ɥ-2 ZFE ײ,"`6awDt ?Λbkbagg$!JlP(@4j5n/P(}mMXZ$^m"$agg^BAfȮFm86ࣕJO?r>/7j5V}t]G*$Ib8::^zCuV$ Z/_BUUܻwܹs`4} 0Å"=dt/b BpTR(Rq̅Q [l6-QI6jt)i`(X,b<#:eq. 5JەѰh4"Md!%Yspj$ĮX|}dl6h6F4MTJV . +ACXaӧOu<ٲ,E~$3)h>Ab3;,61.sN J%*Fh4N甹N1kqHR&*T*h6$ |)Mr<8(ˈD"hl xǻE"ض YYO_Q.!2^| EQd~\-drEthĉ|缹hӤ*rL&M09]`j(˗D" + P3Hz۶JPQ,T*bga0&7Fp]Rm.4tfHxG\.< bBPUj  "%EQ4 \\\@EeW+N >mAxRɾs~?t^#Hu]Ht,㉯Bf, L$J_s-CFзPTKLCp|>^R^!EYHOdiZ,bꫯt:Gd`gK5 \.3@Qql6y8 12Yt]F#d2 fΝ;PUeqw|>۶y8j&fFlhZƉ!.V!xFeJGzf2?Sg*a,X,(j!8lI$>Aa?뫄mOø˰ 뭬pVXag5' ){%I,;?Yxn Ipv麎^dX\|"*D0r5܁{ͦh4s}=c8MT*qfa#m NmڎWwjp.LD,:m Beò,iXHD2 D,] #& ڝb>w1NqppuPUx#+TUEZl6ƁR1,B}4oϞ?=e|1@D($DAD>,p]yi5i`G {X7I[y;mz- m.!5s3b1G4ff9)p6'^\]_<̶`?Jm>o6(jZ*.'vDQ(CC:whhG -2T*x!ƣ=r<rx?Jp-;ò-yDQd A*bbrHLXd rYVKl6k "7_r|{Ay o/8&B"ab.%Lu]N!8%qH @|8fD!}7(aX1?qv ^QA``UU ֜XXVH* xC $x'lO*FۿdhVMf"n#13 f)-υ dP΋5`CKi2K*ߏ<A@.PHwmVXafT8h+މF7DgC:R|GӁ$IOV*f!2t]x~ &5K6M0` 6ڴMMb&-|`&h4$go6:6$Ib$I<rm+$&>}RZi,Ywi" AE% ض Q܉D"$Ji EQZP'ryoߋ(U6'? imAOllPհZ [(J22h jvqqq~ǝv}L&)CS!%IQT(FXq0$bZX,\.#ˡ"XW8kܹs'[e J>(ґlK͆>Fl(&\4.Sz&W;=eZHiivtjrp4T*EQ*7)b b.KiUH hK߃mOSI®( >p|>d|Qs%2z^wQJכ 6nsA∃t0NQ,9.PlI@s#11KpȯIryuq=24ٌ6<$&IQ(4/|@{0L?wSt:ܨ޲-Eu0Tt]p%H~~N$m6|8"Lh JTQs>#G U^uVȲ{CbKZP&9(F7TdNa&TUeVv'y(JILjV:.d fHӘf u!_A&!u-Wbb4 N3V%7DtOxYpܝfBB?D> p8N2 t]bv\¶mt:<̕c;1 ~B܈d2t:H$|D ^"0Ch 9M13ĐR(B"! 1ˡnVL??RyXжmb1V+pxb`0@d(L?<ln7Dw]xR B pb>3 @6ūW^J`Z-?kl}x\.X:;%Iim2رle2`=sŲ=eR)T*V MST*$ 4[[E̒O=NlH0L&P(p}fk@z^NئgelytKO?O NY!dYf! ,Ct:CܻwNY2@EA ]I| %iZ~  6 b)'d8sfpȟg:q CJ#QU Y(>H "].ɋi(JN "`!a.Hp`5Mc@2DPxCLᄮP(pDfa#5df$7l= aH& FreKYj+#Z8]ȔNdbh4 ӴX2\(*'pb<6{~XD"gϞq3yMjo[cX R7D; |f1EA`0faft:lSLi@R!c: 0UU1y B9F"!ﳒݻX((_IE"`k5ᐇam, VEQxK)oM$Ib1\^]_.F2 9H; yLJ u8d2Ʉxz_'Ql6fAȲdq JnnnP,5e6#Qqqq0 lIW'z>4 oDD6Sr#39&ea0E[s z&P(`<#N3, 䣏bFE oL&Ax\MoTBB˗4 j_.qȲ ] 4Ԝn6̌p ,AՂizxsrnnn\.:xJ8)'w1grGBsrOP-".Kvww>[R*yppSNYhZla8>>m888`.,ˈD"|NE"}nCQ47,RA!& r4GV1a,h68>>Ɲ;wl69ږ;[vq>_NVW +߼  a[[s? iP@)шd{P|).$:7e1! (:[MiO8S+k?l0xd2̷znͮ(o=1w]`sym`mClY,Lӄi裏*s#?8}֕!QA, LDnJf)&!NcyPTsEB6'H[p\]]aя~ MUU!65Α)\ׅy"ǃxN }wr3FǍ1sC4'I&FgUUe1Auyk9/w~Z}wl6}!pl6 Nfh6P&X*un"Arggl E dvc i>fy4F'PIpx,`HX,[,vl@Q\\\8n0x$ mja6Պ!ě /) i2 [H 7' 8<< 2 Z)o mS0"ZR- 6l0x("$&2 .y 8AtjZ\aVXO +]rj_4MްQ% NNN0$bR{BF#SljZ8J@_xT*5 L]jt91K7z4&k_` IDAT KzN1-W@En>"& &eY88l6< 8(ˈ㸸L&r`^zvl&7kxrz\²,ݹ|ΖPy* Ft]}^|͡\,"x<ۜ(]בL&!j&7'[%ǃFc0 r3h4cl6`B< @:Ff ϋ b6^Pqvڠr-n;hp-ӿ4Mϱ\.tv@i_`h4'|"th4*@¸%fT*l6 xҶmr9B!c.Hooˁ o2z=<Ҏ@boCV7l"RH$PՐdDNixD$7M|'DTeYyZ-bib:?/*v}w2aj5ԓ@ b/_\.# ,0 L&eQ0SQxꕜ'h ϋ݈l qMDQ2b2^i6j!LJ 9xPuLSz*F 4Mm2@r_ۓmHΤ%^z. 0P*( 4Ml!j(lF(<3U`<z>sDQgj$#Jڇ<ǃ#sFonpDT!HR\ERT#q 8)3\NCKS$A8F^ޞ m4!H`XszzBh4N#,]f][o}rkW~x^O2ۯ'~nї˥db!lMF"u];x

0 FL&q>  ~L a X m˶_KrD:ađL&q||P(F*x  hD~˯hTԶ7?x"j:"Nj! ŊR~;U$8}0MS"^; y.ݻo$'ppddKO$Ml6(Gxf0MSb0yy Lbټ|>yP*~1 "=<<Sl8==x<Ͼy-ΙLHMDt@)Aih4£G'C6l`&<^/N`: P(`61 F L$FķۭA}yȞNtMt23`[6ݢhPc8`co0dZ0x(5 N۶E܁*c(ٞL.F\{d`H$PT<`CEi5ZF8tǸu$өlěͦ4u!l"4M m#ɐm/QfTɤCD|B &ڟB X .Fv:I g{iHR2,mFln$ G&9ly@[ڲdxD~ DmD%vE8fE$E@f0D\# JUU;8889(Eaa2" Çur:Z00M|?dq#}FD"Q70׶mIn899 qHĤZ&*^ۼP.tƺZD |ބwZ2 ÐA9HR^|( }Z7MS Z%6"\Z$qXZP(`JRIb!///iNOO q~~˲P*߬( dFp& 6bQ6_ۖ#fbx<6l豏bokz8==K+4nW|# !q|O\0޲,躎ۇGlܱ#sJdPrspX,i$jF#B!t] 7|PlCazr!`0+S dFxN{q,[Z]q~~. F^^^J* UOx:8992zac_9>>vxz=i5Ml6ß?釧rp8R$ zF&eYHӈr.  oUrYn[a(u,Hwޅ£8??i_KKoKyZ+dYX5*W}&H$(JBa$...Q*EAB8#NGO{{{a7d Ydp0 1uS^|y)B1eNmDxlNDZMDh2 Q,͜Ɖa6@wX^8::|XYwÕ "t]iIvEHבJ&ٱ8 pR>}x<|;/~!`0Vzzz]%ч_ p\dl`V&ͦdHC&7tBe!Lb^ #k# qn[Y-AaFZ*pgc%srh$6\.qzz*MvE\FٔWIg$VӲ,T*%p8,`n0ҫv@,[%^h+(an4uV1O&i#G"<9Ld`*=i6 ~!ɇiA!)jρjB?S駿 LrYְٻsNg$VP(`21źc0͠((K(m{!NbJE6b1t]$I___1zH&v8>>Y˷dO*Y}ĆR1F?lh4dn+׋v-VH$PX{݇!rIy zHlΠ7=>ɤ LD2Z;DBt:PAhmHҒRqqq!LRN#J6h^{޽{/h4~/IBT@T ~_6Ƙ& @>}*I'Z nWWZF< a4M\]]#g(r(X,Ŕk j=%* Fؒx.Ec-R)qb1l6+۷ok:l7$Z8QTBr9a a`8U)ϟ#`ooOXTWm躎T*ZaC;?p.r˭A[n{`\䪪:oD6`B6&ڑ F#B!v4 d_|HMӔP@|2 ҙ$ `0 jl6EX@ GYVnQ*`hq̦}0œX鴐i)b\.#u||^ hR)I`F&o4Mb14 z=$ ,wIQ >͐J$fMh4B<G$?H$vw g#( J#њC(=88zsR⁾b!6uo%߿F2I\&o}#l.,ivT* C^ӝu_ SKr ij"oآm۸8p("Ƥ:l uf@t:ӧOl6:P* †$7fA=;.//qvv&?Za<c_Hj,C:F\FPׯ@"fS /'m9@;?{fh8Ch4*UUžFl.G2B^SUU+n=Ko@,o @)nl$ӈG7TN+X[׋"v\>XaH4M2Y,fM7-z4[_2_Bl,1=ЗNJDz( <^/jr%QNbX{<R)ٜeY0u\:r_6HhϞ=C",\.J"E4lpvvjb(uC(TUEVC @Vjt:-6z-n-(r")'߇ǽbW D4M`E vE>f4|>D1`D"Ff$W %Asvn,Kbf| 3˥ %ƛv BhT*0͉ ,HX,QrɡwJ% e^W3~_dfL nsDs2-ġ*no嫏=zCgAbsynܹߏx,>&T(t|.0$n2KÃxz^,zDpxx(̀J9Xl6 EQFEe1 oxx Xt3PpDƖ>H$mWU`Zb` Vu]G^>";,sHF888 v @pXcLJl6w6X6 Ƚ6D"h|>/C|>h$p8,`3ɄXTUEHw,:ɤhVnl61Ljd5N٢8` ׯiNu]gV.`ac3<~?epX,P,e0O_WܸKr+w[nuчσ9<B/i v[%|jIę߿N&;u0rD*p0B$t2EvE,j9N0αPx\v`0/ H0mٙHi X[̝B06M,w dh4*l@MӔ>471'TFb@܈Z^~^= Jݷnqxx(|>/꺎Ki4`0 pv<0w_wuu` l05Pn4ؖ$B(FիWNbp-cx۶!ecX,1 PױnF^煊PNEQvN>[a(‘֖-<7(A^ >vAooV87sA>E@8wAVeY"ORnz/F/L&a %0q E b4rK6Ni^^ hV~3|Rbn7XXW( GڟkycDu H RH:zF6iu~y èVfFz®yLn~?AECC0@&A,g^y`kI!\m'fcod$BUUT*d^Bp, u Fn6+7=jVݵZA4AkZ-LSs, Ij#j~U_n[74[o}O|ZJ#NE`t:CӧOAXHcHC90 'qf`ph~|O?T(lIb hE`le#NDQwlpX" 0$*tS@ik4MR IDATb1,KD"a2}z=D_s3i# p.H`>n`44M*@UUDQD"t:B0j% bX˗/Nt:! 4$sMt:E,[+& n߾-tP(˗X.cǽ4MzbMDxV%d28򺙲ZP.Ja H$ !~HD.ۿ}guO-<6^Wl&iJEXz=$3HRMEbӧOQ*wwb{9 Es28@vX Ddux<b1sV+ C,Kz=,W iX-I,T z=Bd2A.CV%68)(z]X NBAdH$Ds-mt:/4 F%D"|G+$شED/-)^Fd2frxܿ(˥ %y  X,^D?WV`X+ְ^'OUU P*x-at:jɽ}PǦ)V+0( & ܹ#* Ty^e#}/ܸKr*w[nPH9& Jquur 4F%ueYEi(ש*~ <䪪*rjMӔ׽a= G"d3J&t:E {mm!Ei)e %eaC6ϓDvCGҷiE?)n6Ŷm;\N$7dZ-TU\]]ٳgFNێ_0L0D!3poY<ȵg5$l6r9|R U.rYF>G}$6`0l6߻#kl6+шfS^|aH!߅BA<f2, LS#ϣȹutt UU3Goo`,>9CZ*ފFB!L&Qmrx8|\.4qttr>qH%1]DP. I+'dڮg %TZ/]*Ŷ*6 b hbAjX¶mw 0N%>^;O?ϟ?ᅬxkK4Hz\C63`Yö-ܾ}ډmHD^*rK|4ŢD#E( ygi  Xөv $2 If<7MT*t]$RuDQˈm!4s*LpCX!TUE$A HZR0[4[om=~?njTJ].xү% "d\j6yQdBgggo4ܐ10  Hd X,kضX,w dR".//%q01H+[ZM|Gdw8B㱰 KR"p%6H' ZͦD5M CB!iȨi`%FGWWWÒQ(Z$N4Mt]\\\`4B/߹sG6)吩\.cXBd@gfMA@PG6AS]CK@l:on+x<֪( zh6έlm/שpxo ûBAv~@hT@ N#@pF! v^ׯ_K̘rG! T)oݺ˲$ zp4 b< ;T*9F0p8(b6g(FQiZ,a 尌|L_˲Kxub bK&a2`Բ,|>4&ضt*N[ۭ  r`>˟s2fnEfx$Pvz@ &'Jt- ^l6+<&ps&iXV2, h4yQ+b1lQkJ%QI'ïV%)9~_X<{/C7-z;4[om=|xg}۶1`~v_HnB!'0TU}#7뉇xT*z{6^|)`iJͯ-V(0AUU\__Rc<a& Àf%s(B&mdFPẸzy v6(iQzU ^g>v[6`|eIr ggg|hZbIM[dp0]Rl>p)ጼwݮx' 2 1Zp|| hZd2Nw}}-$SZt0LPVX<#Ј-9bLeYr6l?v[>/vﮯ_uQ|.ʂd2I[oprxh4DՍmۈu'ڒ^Qg!,įk{<!Ƴx6|>/0~Md l08ʬJ"SUUŎNQTd@~ ᇦi0`_,d0@@=z=ҪcIr{&x:̈́'qxxvx$MrzVK ~3B*XJrZ|>/QgX,S*dN%̓U8R,-d@ wѸ(NyDZ^ž3*^xEQP,ESHXTrr n[Y~zf۷%g%EA2p89zjL{>z=h4%ff(B6|-7\Lw_S]VȂ-BzB=#rĭ[v45PUU6lVl$/0~M>ZzVf 1&;i^.ndd\.5޴P5 erQ\L&&/i d K$kaUЦp?qxxUUq~~.lbR$xfIZ'`: h$d2)Fv. a\:ץ!QH1a  @V8b0\ܶR)I6xb!!N[JC< _Z huJRbOt:PU\>O:nyiicl(_TUiL&#X,Pt|(ΰ\.j01 B  7p3^mV;@&CeE6p8D*eYh4ag>5{p8t.r˭A[nVw_ftNGh|"W!3ɓ'zm[̔꺎}{D""m["<(ál٬<(ڛ |~?̝D!ʃ7w}XL$+PniwX.5OeY"ЃmVl6a~$޽{'n\֮œLt:^'O @...((T`~BFCvlTNYQF>b! P(`\JF[YFWNSxO,R~_,6b1Z-ض- JXp ހD<GE<G8ƻヌG!:L]@(/_d27dDxcZd3^%zR4h&!bNH$@"_ L*t:$ P!1XL"MD8xz-nYxtPQD1 dK>7`R9D{XAj5j5EvE-$>t8"LVfS c<~XTx\Cڲ8^h|˶Po16M4uEgqBvLp8 ׋VF{SPh4^~WUS-Fh4x<ܸKr+/w[nu>şmx>ِm6VHDGh!b<(!S$ӫfMt:E\h4BXX!` C}.~_UUEPih6, =qd5^Jpqq!֩`d<rJ DhR:>Lp}}V%zF2D:eYQ!HsG/=ed(T,EA:F$A>p8DTBT M/5 l9x4 ^Gd,|>pzd2h7W*zǺ}&NΝ;HNߗ!@/^d0e |H$n 0P,Q>h48==+NUNGoX XLVnmQjѢƘFEQ0puu]ׄ:$ 4'zb ǩj '|0 ӝ†C^lV83EJFqA"Y޼]r-4[oam6|%I& 2K$j5i@r04\2sX@`6zLzy(vZbXZҀqêi٬lsu]G2M)=A ^c>^# fM"=T/ : b$ h41 Tiٳg2v˚H(0K=nrUQIgkd<y?yſ)Ç>u?mc:b_<l6^nEF(  LӔA]  Pqqq!*HOXl?d!j~xޞH, l[D"$^3ɠ닝Bx0Tc[a\p*1-Tz=Z-Qٶ-<ڔf;^Jѐz}}-jz MDEXvux>XT6|HRl6( uGGG8>>/e@:GSz%Q_UUD7wޕP(ap.r˭A[nVT*Z&Zsx<@Q|4uWU$Ir9|[ߒm|>HējdCƇcǃrJL&#\.'GƙQ 8.Gk:a6^ Àqn8(oOrTUNSs,Kt:>3ܴAҲ֟p8,6D}eΝ;l6/^NEbNauLӴdYiE߇4MCAnWiCQ\]]a> oȀ#N GdX a ͊ rr(!ȜmOӿ2/^xxAD"08Oڟ˥~F mhYj4Mi&!H|yV+:뺎D"!Qb!*`0\.lv- *iZ!L\.C'7KSa>,KyOLw4]jTUdbʵz\.qrr?\l&f3|Q?PeYQr!녪bkJ"ɠt/Ul8::`0@$0^~-6>2^6* BM{M9h4(˨~Dlv7[>]\\qnWV-zkFQiU-ׯſnAvݻN _)OJ,']i9N~?$:(44-9sK57!n[vF i<~X6T'z=VS}SX,n%@|6GM2 ٬0B`8:rYT|GGGc _|>r6Dkfj|ɗV\懝Nz4M,T DBdZ@c!GKnn _4MzߙL@GeÇh$LH&h6ft<%7PsMOScb1!S`D"uy|X]<- dz]<|?BA( lV8l6$ CiX9LvB׋\.nl6+wBh4M$ %[%nBJ4;#i4v\/H'hCߏf)2L&# aX,h4g<T*a8޽{PUUR;azw0=~?:ݻr||>>t|i$i_~KPcn6PL&Q(8i2<缦|>f{ CQz=R)f3;r9D" = ѧdȄa,W |[B,ãG:_9i۶-... ľj{g0j+^өXAM 3)bzb߿˲l6cOeK9astZ{5jlP,*l$Qui\I'X"ߚ[ë+Z-F#y܊+f) j%[\|>& kݻwݽ6"GWKRIč: # È0M^Z-X:qDbQLSt:s5 @ p|AAR{55@x1:l;3$aġE$A2yFbW9^zf |4f?, uEba!ё<flh4 EQpS^J F]8 n(JSJǡlq`Y$Mbi"?2}0 kQ=f3OT80Bt0`*b~_aZ^u>z ;2Z4E`0@(NF4E^YDkYá?~d2qd]x?x\/n[t:Pˆp8$2EA6}Öb2H}Nb2^ˀS4$ tY% l f4A%P8r%Xʥ'4M. c hZ󸾾5LJDZ/qg|L&[ p@6 ׯqm Ƹ5˲p||NRVT<Jӓ$)}]LLS ~B7KLSR)ܹsDba4 O>E0ٞCJ0Zp>02HρHRFzzi#_QZ-6҂e@"_,EJńh4H$^eߩ(\nC?/j<|?/6f(ߺ}/_4M\^VDj>L Ѧ))&<1LR0MSNTF#ƆBQ葽R"4IA0Dݖsh4 ir9}~8-V|^D$j*C~/i"I7Cf)i+cuD *j%ZXzݎ0JBb"πI/H^z%Ǟ":>| ۶Tvz2xl6߸-N^wgHe0KKC\Ŕ3|ɓ'xP4nΠl6!v~dRX<j* qrB%a ҂~gWW/zu-r nW^R'-rLH l< -<ɈW_42\-WW,NX,{アN-&b8w9w{-;|>;#@ \.'VT*%$UUv%4Min uVBa]ݻZ($kFy<R)b1Ic 2y؏cX%7$>3P({ʥAh4a8%0  ϣ6b0[Q.:~H=fx(R.qyy ]י/ACP(L&Ê3t+sfti^\@ BRpZt:d#N`kq~.DȽ=l[iNP1 јQ]zWwZޠ+ֿw|f.?J&G# AH˴HSv^0 cYciƢ%P +Fio;0% %"1 Ig`VH%Sn0 G.D F1L3lnDY*lh8EA2ȕGH?9G_t6ʶ1N :6֑m }d\ >'[Xt [d"X\ʃ%IBTQaLYqrrnl6lv@ `0ӴӼHtph4|Ӵ#4-,%9htttbиjL&Í Eq=ثf)3sƔ.R KyL&ʀP<|?$I χR6bɉ5dU"Y:c\NO0@sr,N$j&Àl1~!ېamk"eYv3H 8٬c[ lŠ*% D<@($P(b<֡iq(J  K|~=e4?r1g(X ˅ h4t:<УAXKuR~`nB\WWPD1]CCppP$9їLL&߉aHg26s;y<@V!l6{ 1`,Da' "ֶ ˴L$\.;^ܥW^yug ʫ?Y}Z,^'Qrlme5a&^| XK#cfP eY AQWW,K%b1Yt!2oJ^x^r)v`: h@$ BQYX L_/I]n {=dYcNIG{|b{{{8;;xE>GVl6ё+ Eb`#m3;KܖS yLTdI&0M?gC-BVd2A&ah4ǏlXY!"FI@[bB| AP$#y IA4ꊢ~9,C,ʟv:w[wxxs۶+$?~̰A,KhFL&|PH@EwBI+R R b>m&c BmA@*h6k{xxlAx<~o޼!>~?_!m6)Hp8d,jBQ~lB:vdbl6͵(N$\$4o咽d oqx%I|>r5); fe(BUU$I\__GǑ䜈3/x||h4kGb8ɍ|>g99$"'fahM\.zvZÇ8::B`55(īX.XjNQD"HRx{":V |4q 6z`o޼v 8<xaHt4MS ٶQyFՊ)咛`MӠ( ׼D"Fu|`2?BѶ~zAhx,ZrH>HVOj̀ tNsn :0'ਚƛnYn=~(BZ6T*[p8\.,@ rLmVP ,rAz=V! a~CLR66%22 ޼y`!AiPr)EŨnKN4xW:;T6/7'O=☺#}4Ə:R dJƹ`^;>?2oGaLQL8 V 8Z{VUG NdIR c% % Plg c}Bf4n{)JVet]f3}LZ dxn>Hl|Nlص`E UU!jD+3nCr[7=Ah4 i/i:v"d1JqTns9t]nlj `Y$xiDH "3 h JObb1 r/-b?1 Wx%KdbL&9۶YF-USUN~Q.ʫoAW^y'<͜;]HrHi.o2s7 LӀ$9tCbv !,a}~{}%,ӂ,IC!W*!s;yFdǸqp@ɄhEQZH$xD`tVŤR)TA~?6.rfދo[ >BAurfvb-Ui$Ddi}ի7wwR7>hpXvpyOwpP \`eP\Փ>ϵD%9:F 'Op}}~ϲyj@)FiJbhۘfH$,[ J!Pă8`Cd#UeYŠ"GP+FN!&C G8UbMEQ$5)31'VNNӜ.AmR@UU&O&f9|>VMl6$ NE*>Y '''HRh6x67p (pg$A6E:52 o>WbzY :h}vSl6YLnjԊ"KAFxɄHdXMq$4Ml[aXX,d=`82dq!)9mbisuund2gR?i?(e_}v7맟~/$ Y!Ir%~=wzǏ~,nCB @DX_SƐp IDAT 6 Vt]LJ~JߏB ,u C$I|h 3XAIB~-^)ƕR^B!4 ].'$Iag%PK͆PAP,~P K&Ay'=#n`b1~/RW=@xÄH$~@CY PYOg4 L&|W 'Bwj0֒m#DX.[_Tr ug({:;;n`0x; ,JpUpyGM?O6RCl6[`jl;zݹ[D݄Cqt]Vv_G|fts$ P2QhTFuOEd:lGܶmdoIy5 |86fa^,x4qmc{47Y02e!NCEAf37Man~p=>()b:X,"ii*2m,v]h\(Uò,ymmުꊓ)s]deOօpYRD.!KEQ8qpe!JЍ%H(KEBu˲x@3nJn Jo&Ab^sٹ/>P"eYED"hZٿ/ުgϞUH= iTrMzqo\VǪU(2 nnn777D"4F888`fmNeKPU| ve~))& iKc*B߇mf F(>bPb:loZN l6B"?v k$}>d% CD"J%~DZ#1xҲ,VQ IUU, `yi"P(@t:$5N({bgrt:62vѤz].ʫoAW^yugiZ$d]2mRG%I- P9 KÉX,f UUqpp\.?VΆ!{o(EM>IM@@ t70L6T% nE4McܿU zy0w-'I>KAJ% C憕 0;5x)lE<-%?sGj~z 4!d2(Xj,D^4wG$G[-7w[N$hÝH$#YCb1ηbWDǝN?dM&80Y`X`0@$L&l6T*o2nZ+뱥Ç H%p dYf Yno8[P(`0ȪT*6T*l &e茥 e8=H1!"Ea; IA&˲X Ys>3 2B*cK=zd2|> ?LS!K0۶1a6kȲ@ @ FP7!rj]e,KnIiR[R)ܰMEer9mel6茧p8 *R`& i(Cџy <:;zsΥճǏZzq^y7Rޠ+ҴO'<`(0 O$]$r9dYț9Qo&ohh@^q y$znpo$$ um|”`Y)2 GƱy^c8"pl#Z `w\R nY Pfpvn4f@lp8d[4冂k HLfoB4<} m2@eb1 V+q=VU, XMkK&YVhmifB<\ uL&vH$-( &(0 t z3!)8F1 I,nnnf|:"?_ow}ߝ764\$EQxXaVɱp}]]` PHDzM *9[IAr HJ \]]AE˲nvttK&0Mu΄r gHl֞h4|>e4~ @ s1 łmdv;,b)ϣX,T*!LYC)>& [$0t]gm|vyy>l`Y>imWK)oW^I}'b!K0h40Nc(4==`%C1av6TUe ?@LUkX7 ## 7.H0;Y9n6oIpj@bl'InI  p|tZT2ɄvbP|,ˬ 0At]mhc"9 ( tVŲ^>S4MV $ cވf2~>>>FPM+=T\4Me71V$16M(eHC!^ǚ6DXAro|>D"#Җ3^.$IBBVF%0(׃b?v )5:?~F~όl6z ߏjuOի;ݒ&_C&:1t]l[_x%U V2HUUquuX,lW0DPzfEiD"K&V0 bZa6=@ `0d2S̐v|e sR)>[$?]OvT `&7Ϥ`"nfA p8|{"+hZ" /qQnl(206 j1r8c]זH$p||V 0 "& rh pTQS<~uhM: "9eYE{{{8::$I .}tSq M85 !5GѨ݃^+^Z LGW^ܥW^yG/oW^I}?>K f0-$Im pU$p|Px9?xDQ.s`ZA9cnE,"5iY(?EdYqAT?tT*z7xz=Yq}} Y1[!J"p~J$ oft:w>8r:"OqE l[nAF#7kA$d24 C{ҟ]9|.c Z@kuTUΉ O>et:eD$Al6`0%*b1>A`]ٸhN%d`!Lb\B l%IdYd2{|>K5mATNjle\.3&,ع*1"$m!I"1_l|HGLJPd9̪Njh4K^m FBJ Vp9+jh( /_dfm1)d `ER H&ݑ%LB96,3U׍FzW^y~yl&KOf3u % 1mR׃hS%af?fY7oXr*IDX'vh4f5Bhh6S#a6[99* $-l |>ge}Rv 5d-)x C$w ߿  P<Inp8p8x~%c,KeD"T he̩T+D @TbmV@d[UUQJ%Lrd{`=@OV08]Yj5֟FDJh UU~}񫻾_<8L pi0DX[fLD(;Ubx|ft4a^PIEA\\\V[UUrd2c~%yxn#uׯyBH-yu]۩;YIHR~?FI[GTjVbyaO׃eY2UłUt___룴h4\R|>P~D$&`00Z2߃MWv;N Ϥi*e1+b*'͚puUՋ+ ʫo=²#EQZH$ ߿t:[42J:C`NRi}tϟ? !m>Gu:*tT*`Ƕcft:l64 Gj` @<gIo4i#J85X<lXgEQP.Y1@>hđdr \.X,&ӧPudC۶qzzZ5Mnt[܀]&NcooRo4VEQl>\޾WsW1< IDATvlG˲8v>cFV&\QmNgk`0{6!c?īWSTUV$IN"mz2L"&Y:G\p8 JĶ b\^^" -"_3 c}6iH~xl6nˉ? Vаd2*tO tNr9f3}L]X@X,XeZxLi3vDDbAppp04eYfkbPUy@ wI>^MӐJsE/+7h+T>Y6.Y{e'$"Nc1'L~77ݺ-l\NtC~Knu]g;m5REwb^| +>?  aawafl>k,SUBxgiG R4 |?dIFՂߥ 7>D[WDDPu#ᢱt]G<@R;=s|i"8) 9$$W_6$Y(Idib8b>[=&[ B:$Ib4}7X$AFA]&E׃"J(ns#@Q4"Hw Fe|0 Zb|< MӐL&YfF!Ba;yOW#qaDQ}d3N%=zZ-ڶ7 t^`0s3Pz=L&²,L&rQI@lYQ,1XB<RpJ>ۛbN*ȓi66<Ͳ-#ooχ{ y@F@W0mێmC*@D777yy} \__3t2.IHd6%e5 @j8;;CejBD:F8FAs,)x%FeLR̩!)hBj%yt.g ==z7onYU,YYrxxttL&L&YkAeY0݁mLU9&wiYPpuu2Y{hBy4E"xV*Zzq^yտAW^yg?WU^IiwHFZj0{{=B!D]Y+m* `w:fms>,;IzZh4 ;ȠVn#a& ⚆ea"q 65͆9e+B69KY4Hl6zNևgϞk9ϑf90 Ct:$ h}Nfc4j!0 `;{gl?hԤض͍EXr9V+q <,~ _/˲0n( >C{=j5B!u]in=zĖQBmU%?ijgpqq@ڤS#٬ϗ/_gϞU4Qf3W64nQ((8::p8a8I#4MS՛XSȫWosZ$!4 =,KR)n4Mc?ZJ#V =C\`09z=JVY)ͅ)UU9,`^ j۶y:4Mc``G"TUNDQF#4w 2P* yYMH$8UG9M>g(:j  8g͍3Y,P(xHdxMԐhA}o߾XLI$kf%[(gh@o|>fh,ʩ:Ġq,f,4PיC A%<wW^4xW:>>޽{?-,E]rbr̀+$fdR)a0p|2 oַŖ ܧR)/Gw3uI@ sbL$1;%b,bm$}grS}¶p8jʛf\.x>>yZY?l6Km6"1,QzB0m`)94M,K$ B!VNQ3I~v[ܦh< AR4![hTe ^R)k}QqUNuFZe*0+h9qzzzdh4+np v;; ?c1ĒUI$+!01Lh4˲.Kr9,IEÇpj{=\\\@$fMSe ˗̉q9zww:BOm[)ж[x͚dp2e#݌*9t~eg[)Kzջ_ʫ4xW _zׯa TUTr7t(Loۨj 3l6hĀQ GZ[p9 Ap`V,ʲ[MD$@ t{=DiW+eTUN# 3)*Ez,IH%hKQlD k뺳4LSi,)tjpu%ˡln@L ܬqLg3a 3g4Hfɟ +1an" HM@NRhz&{#Țhdetb8r|ceJ(PUE quunnt|DzџR@05CW7|{P(SWUdD"LS[ObXpSZHCt:/J$ɲd_j4z4r#IP%  `!"php8Fԓ5KUUkLShvd.wV lj(F(tn6 C-JHA~<GA$M?%SK&H`\:qn %t:4i |ߵm&-Yxt6˄x4dan:-* R,Ef" @\~'DsyXv(jiG!'& _X Z r<, 8]sJx!,ƵV+VlQ3yiv+N$IB8, χz%0Oҗ.7c^\a`0,l[wW^4xW*s#z% =gKial6޶)``0@݆6Mnsթɧ}fMb$ɯ3375<֌JҐi T @.%@ GMmF'OB.Z fXkfjos0"GE ѣ?̊ w7}"% .a6I:ÌϿXu[hCm3إRIb٨p`Zu |>Z-t-R)|g": =pPA%iT*Bw\‡h6@H$lQH$$0" J0 m٬ Ͱ\.ecyPƯ{/_~{Pk|V+I8ND"rz=dY\.pn'0w! 1 b1"G0 ۶J6:HԑʫL&|+qppǃ˧r|` 7UT x^v~bt:J˲L&% Ѵh& d2)ײ24 jsrXH4(B}ɉpd7ҶA9h4? ʿgZ>E,d:l6Q,a&|>#!`j,z=r9\__?Gŕ}cx3uzJT3L c0 ǖ4b@4KllZj*2zF0 V+vrr<]jWk7hծvVX,ŢNV%f#$[ɬ'nj.n7dOd:E(zʲ| `& TUE$y/#2E Ztaa@&W2 LDVC׃ulV t]l ].p\fbd2).6!TV!m^Of|$>rZ!L_(hq(zx뭷i4S tnHUQh0b2HFjlD}>6hXVFbz 1Ɉ Nco7 }ҼTUh4BLZcLil[4Mƒz):sZ-)2bT*L&#iJ#VI'׸?xpW,k65͜Mq:UI2e4!LJCHĥyFH3"N8;;#_͖zVӇ|}/_k]b: Zf p9 L2 (JE͕Jī?*@ $?PaC x<.%˅~b(r"ooUxhnQf[YB$<:0ɻ;Q8, )[,r=M;U%=8n+v Ka<K'^"Gz<!j\.'p`MӰX.v度:񡪪 pg! AHrhd@o-i'T n־[r^z<re0n0N:24upaX1J2 6ۭDŽyvrT [osW4jWWXO|m[kb4V |m4 F@~V P}uu/ {<6Fb ICNr"!L[dTnH$ir%(v# a8V˶r8]6߄. T*J~z=iLӔ86E4(1MvD$*d2 ٖ2LbvH&N\}0{Z9r&/\¦ L(mTUD6tc.E-,E~{< #fxJH$C47ט縹NV% UUEǝI4G?4r̟כeT(H$#{^"(#O>EV}Lh4Ty||12X̶H8L:Ά; Ll6l6+R PRhI +-ɳl"^ pW^Yx<QȰ{Bx.$ TlHB^B:!1'͸޽4Z3<VKRZؠ3]"H JC;< Ð[#g=VE(.GD Lzf@\N:nvtP\t\XcQ<~WW\.J98Rk\ӧb>m*h'yr_%awqծ~ v]T,UU9AD"A`ZMf|7L|(v,$sŦ? ^ˆ  #rBDχtCdH$",n3 CR& o!?N@ |>~*p/UUQ,1}5!Cuj5@G0MS*UApoԆP(\jx11vTsH@@sHx%I^GD>e }3V`oi>5?~h4*(lڨܠ# {h/`4p{X,s BuDIG_o}l40 MS(i"-TEAƣGh40NnL&"JK3[o ԓ\z^7hmMt >dq0 'V-޷yJ\a2&ʐ >eY8;;B(Oݛd0Ƴd^BvJ8$w6ɦ?ZF%!~v] a܏eJGÁ]ݖv}Qj5躎f)}sQEAbՒ FEQDd8 Q(PVȁx<(h4rNqH$d @RAbd}0-(IUUt])`HMr^~ ]ES*DF5|X4Nt*%j&-Q³`B+|~">ծv?G ծU*~7v8UߏcDQ}X,`YtZ&91.'?dVVlVla!ZI9E$prY6M }RRvJp}}-M @_0 \.qrro}[xr IDATRWOPA`0@0p8,"Zͦli-7$~MǏaaoo q6j!I#G&e^0 R)\]]0P,eಷ' hq6 $m4!b^ W*;A޹6 ٬ȓ]?-(wz·͏~}1ͧ|hb `nooD<5MQuV \u"NCu|FCuUzC|\.z.fr?'[&Zhl5M*0y3?_UUQF#eyΦz0@3dsOyө\.`WPAע\#vvj%=hrZ9G:"\_躎@ _Z@x+4ܫ %\.vK1`[}(Ca2JgZj t:b:/Gɷph4 -fICד̡ }sϳ1a\>o4]jW v]ŗk7h-F.e`5b\Hv\.* E=y "416N%'''Cq \\\Hfy8a>?x X.WnxefpY`\.Ev̦fX TJHw@B?>>O~R)iҗo}[ɣzt]ǔJs tau2Ϥ=ٙxCHnQ*dx0tDB^j~#DTI7^p;<ϱz d$r@VVDJnhZhZx?Pbu]^EQppp`_׺H nt]F2p|0J5r|>FC^d2B_c-0w\@S0w 6xn6( S!+U7 ,!7V N`PulF~ 1'pjItf4m48 " @~MӔ3<ZCQQl6%_2~KD"@رSp V($b\T*@UUx^D"! b٠I7T[x9P>^O ^,_uT v[8 UUnz F<GE0~_`<8 eAZE.io\?aRiFLZ利YYsjC߳ծmnа]_\}M2.{Cń[4UUa<)NX[7]OSZt:@ Nc|HVXħ~b(2tZMrE n%ܘo6, lBFe}m*lVB O:pwwx)"Q@dR<|>iPhK?PRg`̢"x \"vT* ZD.:BN#NK?B$|禐^l{xo6!̛dEQsza-3`K%,FN@z)NOO:կ^^RIࣣ[lEaS+,Bח$ynsn7~y^bX, <%\8P!z]I^$˲u2|`F]CNt.})aKPרX.XbP/b(\pXXqK%ZիWUz|tEcTpzz*flzʼn,󸺾|>Gەz-t:-2jn~?'WݿE,Xo<0:1y0&pxKŢ>G4\]]\6ㄺMS̜afn`E]-NvYof! |;+I`,TU0EvM:aNFt2Z"Jcoo14{|_9r\+t_dv= HDhv^/5_.0 ] nW}u;ss].Lc;ѹ!P^#JA4D"lʶp(1\NcZ(_V˲' ;SR,B>!J$L$`ryIvfU9`݁g;>'''ri2q e-l6G Wf3t]NX׈D"jl u'a Já@9h6siKpc>t:E`0fEQ0 @pDIpcZb2Aub> D^ + !HxLxp8} y1 A駟Bt v=:9bw]ꧮݠaW\X,l̲V“Ldה{^2mLF6~^O t]=cFm!סW*yו}l Àa(Jpē\.D eib(sR˅CJf3d_Oj{\` iT*ߏk,Yj0>\.S"DbtZl |>iNn},t:0LƦT:O>H,6 8LefPD 6n^x1QFJhg2`ǁUa% 5M1ZP5 8y \\\Uj"ˁ, Z ~V2VK6?}r|_ TQFF|^J>t*%2jr~rRx'T3ASXbyX4j ʤPFbz¼0ׯ_a<#g-@rh6888ٙ|>%ݮ y CDQh5͡d-V*$<_hGjWYAîvJ߱VoS:j`H_p8#aV~l(B^;{dvL&ek(>QLɽ}'jl6 $lȘ=l4~;H~|@.~'B[Nbab]nv6Mx<|>(b4M`h4ZV _m>fA^+ǘ/h6@Nf\x:A<`0"$!>sFG!i l~}NF#E2?\.CUU(=:D1n4!^cZ"͠ a:b%Muj~D1Ǟz}/_fj;X4!fҌ]__?8]Zʴx^T*niD"4 t:cJ%I;CM씐F΀QO=o\jۡPHc OEg}&MՓDLH`xB!B!ER7"^/"܎' ؐQsdYqТCK;#Rslvq c!\.l92 {@zxx(XE...WB(r h Pd6 r9M0V%@ +xEၪ,Kj | Q(@6~/@p0Y 뵀^/\ؾ[o,VK}R), (C~Z$Cuf1LkcY-QОl6vd 8FsNծvSnа]g|=H0x À͍~mْ3jZ[ͧ4MP 9c#x9BaLL zc\A(׿uѿ(pb`C?j*^۳ jx PE<'xb1l5MSFa`٠R8~q>#H_y rR PHg9fb~>ORl6|:M>-nW",Iz8??ϕPHNS,id2X.d20 777bH$!x\b3y-~o(!& v{D@b1MDRXχGAu\\\`0v^:/${Xp8@XG}$iAPlV҄: ^]jW'k7hծv3Un*烦im6d2PpX qTp|^>UEЁqHZ!"BX.h422MSorĖn6Ģ1g3glSh@nQ~_f3C`BQ#3lZm,Kz=LNrB DC0B0Bܰ\@uQ98{6.ɔe?p8q6q}} 0:< 4 %(ZqyueEAb3ͰX.kʱ)á PG(u PDSU%VL&#SqTsBP M`0@^dzma8`6ZE`Yk|H$bCz\.'Cn.Ba #x1)׊eY8yth,OEbC&AٴUY&Ts\.tX,vf0Ă!p8[&?0 D"QqB!Ii& Hz H ?l6omUx4=rR%D"t:b=j(2gϡo*hxZj`}r sW\.nxxx(>P($xxqбlPVe@CvUhTl(rn(vl6~ē'OdQĺ2HT; w=<<4 2Ϗvl6qwwp ~ρUC޳ծnа]]dlXFLjt:f | ˥)R(L&C4+%,B,;eYb1`/..dH27á$C :X&rjՕ!s仡PHv[$Ib13.aootNh-Ul`0 μfz7T F9j6DC$AXa r4QX#\\\X,{v\.pxxGXV"=v\H&x2:IH?@zmg9 Oj^zT*%a2, 8c&`g?&|'=ǂ=m?I ka-d2A0z.//-'W K$, ~i2xh4rREvhݬ%V̄1 :TUūW0̈́iC#~'t: t]z2OQTU B2L|h9p?}uwG.rW?ZAîvVߝf0 zwyGbcEFmS0!WJ^PjHJEQ/{|: |JRB}g#xp{{woRUUDQDQɦun0z9|Օlo onnD?ndPVESUٺR)$\y?NPoYHyȁn7:&ݮ$2PE $m=R* B(aV(!LKR0MSՆ!:|yGD(:.KT:B$ydLZ h4hZVbCyzi{Fplx౩XVC^t:E,C&Iul:` >ci6MR<|r3,Fh4F֠t fb <`h40-: 88 r/XȨVo9!ɵ|uu%zLp @~P($)HSwtvp(JX,^څV;, yZ-| _@׃eYH&T*2츼ky3dY(Z&nϽ^IC֩T Z_0"yhZ`ǵɰɀ 8d@QMpxǘIinbPӾ& Q'?aqj5RAf0DG>|, :_,HR, 777h93Bd2rL;PT:.rWv]jW?u=>儾駟"`˖IJa(-n~l烦׉ؤte8 {{{$Ivfq:"L"HH9s^DBޏjJ"񏀽uPUUQ&4X.Gu@`&7l dfϙ qjh4*L6H_W*=z$Re׎]kZ"GX.HB/p;5Bhar NjdNj#\.=Dz=&Y~#Lv0FH$D\࣏>UdZjQvn֎3ܺ^[|O}>ͦ8xmz^i.il[V$uq8>r>_qZ!ߟV/_]]GAuzceFV OgJS8vabrݰ, nНhd}iȌ9cj5y1LN& (0uؐpN _IUU4M9Ciza@tcmQrC m<i%X=T*4M6~V eʓvj*#4MLST*%Sn(M&qQ7Юd6رXL )%2Ɉ Fc89Lw|^Ҝ*D[aQ@g3qIFӬo[ћN8::&L&,V+I B7y ?1J% TJ O4M$IpH4ϑJTUvgggX.Bp d4!(]/{Zd!:n~]?ad4wdYl6pm=؋Rr)G~h؍%D"!~/26َRf$_|ɧ.ǎBז~\3 [n'etBB\.+qi\.=Du$$­3SPx#O˦1DHT*%J9Ҏ n:N7)L&H$D"\.x<|>7xgggrNqHzdMlA" rd1>7`٠T*ErP(!LV ۍW^$7t:V`0(0^[5MC*7MQ9{sb1t=RE/I;LPbĢ bpzx&F#.d2-&DI+x.lvwqծ۵4jWw_n9pvc0Htp||,,6wwwwu] z6Mx^t]J%5c,npzz*oL PEnbpcY4J|+)^'ggg1t:XVX0ȍU0<${ ÀH$"|>4/ |䉰$QRniܯk(P(fVMӄaao(%Iy=`PWyaBC,×%ɡbPT^q}}-@/ $w~~.,B\. M!|_(=瞃6ٔqC;Mc'!0 mN+/4_?goGr9AC!fU lGG2D"k\v+EEA>{P(Dz,( \.JfF#NrVss?|[<}t߂CN݆ad22$s{]. &B &1 gp{{ZaCQ9qss@ qT6px]ap0~>K-xd2^|0 ~񄁃Cx<mQ| \ZJ(|h1c%a} qXA$!dpϣT*I .DBpa0ZhjX",kJH\N=j9&gp8͍ P]yzz*2˲凂c}H$*ɕJE'''b@c8F8>>eYѲ$~l_udYy` 4TՇz•wqծA ծ~zwtXƖ{Afu]lLŀ"gͭi">~UUDžN1&:J!˶;q3 ɓ'vLk6Vz8|> f5f3TTP(=G8l6C8$AnTj|Cs ~ 7fTD"H$Rz2Lӄ_nV899A6E\FGv=J%Z-Y%ן9lT* ܑfsH->88bjN=)10V~?L}jˍFģw)H#BAG+|nr9~v|ZX,ʵ/8!rONNPV^XrZ"l[& xĽE09#'Ŝ۟vVp8`0^'|>/Ty>xQ=FCunWA0MSl!a2@UUyh;⣏>Y*!{<Iڠ4Mi)I ^,H$o&jl"KBjB6Pi Fh~0! ɖ{*9`=*|vKI/)|>.X-m)3̌TB&A*ݝ(??==AE(捃^Qr,'m;L\?uwDMӠ:nooqyy)@nb(jH'5c"O>4M.Q_,B^o0b2g܁x)wW0Y@Qfb14MQTi&nf WA(x4fzl:aS5(~D"D|6a6G!9v dxvy氋C0zmI|09W2 BqC<zFVC,aMFXIK&e}1pfbcC\Nw%Ä[a4aa00&rp8Dဧ(,^/ 6 8&d`)Q`4 If6_\FRCZrG$:z*ݮ. td󎼯.K{^z}#B!Zqvnа],_pA+`?eb4xb(BTF\o.)BkX+XHn^<ҿK'777BRyD`79www|"C0NE&}IUҚA}$Cl4W@n74 g[O l6i(J,ߞ>b!vJ' 8n  x9={&i899'|"|WWW2| x5oFr KZp^/m/ @ j*2 ѣG(˨ox) gNcSp̆-}ai*F2J$f㏅CQ*rpssF *%v^H @nXLz )J" brg @ P蹮< $p0=ۭqϛm(T*%g0q# !J'xX$ ٨X2@:22@F#H&ㄙV~'b<{=st:-`BHɯ%*]EeiP'Y˲$uV8Ja>^_nW[L|2P=JA@. hTpdb(bQfS[]Btjhr03\.kg}ǒ|BXd2:*]er$>h4rE7ҝDm4]jWonа]vMӰZ`h46=y rfbF=ez$␔rtd Fn6Knnes3 Q*dwrr"Ty6Lp\d2C|>iF& 2dc>˶nO}<c2ib /}q*!-Ϟ=MRI4GlF\.dKs0`&J 4˥<ñHPJ̡ L-Qm@@Rpx4[ [iđ>R$n: ` >c ߟvZ&rsG>ӧOgJ٬4vAi_xasBՈF_vՕ9#7lVTC`L 栏p`0(rxǢd yꕨcjz5`۝?}4R,p(M{\FCXߝԉ-%gm߇のv\QɳZL&e0J X8d@ 1>:[| E L>w9"(./2`I;Z68)`>#0L&%IS, x78x<ʯרТ‹_4pf@u2ZʰO<+krl*z#aX Mʍ޽ Jp\#q!ɁZQ4 x^r9b1\^^ q6 ~-n8Ң튷`sjv|>TUE$&븸f {L* 1Kz|N@tV"Ƚwi@8fL+$Ӊ\.'r $RV7ynGO6Lh4^{8J)D -hχD"^'ӺFQV-)hj"-srJkI*zTOH$AT91b+%`n8"ZJ "6Th_җS*$B&TD[*RV%H8 }dY^C+6׊h4*mFNSi D"! g}rƧ{׿tyxxV% 6Ob<~?j>4Mt V )8NkaL'cc1T+U,K`fĂtZVIQIA4 ZșCD_T"ddvrXOF) 0_|+tJծq_`hدگzqqsbZJH,<dsi ~m$ 'w$=i4b>\.* 2TbPհX,@HuGGG"AYfSp8dj}uu%iJB!XNr9tのnWWWٍ3.7 ٞNa@4=89>>#AqRɊZFBB l#ťR z4 lT vn%ESbłi"WZ`wFcKjpq])4kդH!l4J!Nc^#D[2ʆ<əJrjumX0 )ZM,5H@@\N0 XL}[،zX,W4M&TFЎ=zcAe,K2SQqmo:V*՟r?vt 'QwD+(Xx!V GGG‚IS sa,Ki29V rE1Ć* |^T80 Zl6a$1?b@Y,F: ^|))Bw`*BYCT|@l8x\$UUEلNbXwARU l1&㨪*Z3Nfٖ%-&<eŲz{ Ҽ(rmQ/-g &lPD{֭[bZB B;)wn? _>}~~ _۷o'I_8y!߿!v899A j[o%ŶIl8F._")Z{<!t:x^TUq>px<'|"SN'HD$x)lЃ͢.@4x<j5[0 C>Ol~`dnW͒޾^eH>t:AZXi6P\.,Ń>㣣#\^^dSkƁeD"HL&"f(0줰~jU]oA3i9TsY*QTШ[ B|vF!4ɢ~ϟT*a  !Jjp\gV{;N>M6G7R:4y?T4 iQZpqqUU*\.<\b]t-nwZy/QU`Fl/οwR#OU,UD"fšX{O_J 4MeRhh(J `UU1~GGG@s:@UU+1xs;N RUB!)d3L*p&Mfd vd` $ 8N~kb\.'&5AA4|V/..⦟Ǐ=^c1O q}:dȨ~x,SM\[%> M) Xz&p?I&vi65ߙv8v7y=m?T@ۗH2 VY:3rnG m8aE04Q.*R4?{"k-΃XUl8؞a]icNyb^[Y-w^zWe1X,WBOtLFC lP~*$|^E1 =Rb4!ϋy:ʄ9^#40 JG?Xƶu*1Nd=ö麎\.Nh6X.}Kp+`T*}OYo ~{^^uz=/0G$SIS~`@TaVvtڊͬ'X,5:Ϧzp: BN1[TZ0NNDQmR)$Iu MO*2"$L&Q0"p8Dh>~h  aZ!BQTk5t{=8N럶e`\m7wfj?oX HD|mS,( ݻl&*{ >r _F~~'nnQ*P.eBZШס*ɤ0D`ω=e42@:mb1!O&.r~/Sh½N't](J0MS`8Hx8Ɖmyӑk}6 =9NdV %f礗^az L&8<>}*wZIiáXR6u |F41e84NX711+RFo1 @SCfa&2 ?h4d2)tTpi1b.[Lr}wNL<e4{|^d:Mib <>1vv 8p8,̌t*~/SΉ]p OIx6r7MTU LSb1Q"tyyjcs C$\UUm^-u T*Yr(| ,%p4ՕphhKiu96Ma\.;"ҶXXi3IAr;~6Y~A8ɉȬ !`L&#Q^z4jMw;l[{^`l6f888@ @E*DXvzDNJVx<aCP?DXF&Z7[nX,z("1yN 0 fra}6xN&4MF#X,tV*Ōu/,l8wtZ$ۄr cf#~l0 ir i*2 $"d2ba+r,L& b:6ؼd2uio<:p8Tqb^ JC)iKY2 UNS"ǃ~/zEQ0dH$ rzIϩe[J ?~7@viZ0 Jzvhp\6F,8?c" ji?k>oRN|{' %ǃG[ /_T zqFjTTT8N, Y,yM>}K!8|^"~r\햦A$bPrѣGT*HT~\T*!Cu!`P, p/eaI?3.q\b>޽{>=>>Ơob2T&0I L BsE",Ox NG A^O)l[q&HJ?AdR8>JX ܷ؀l6|8<<H8XIvg{0c0  jISMT.kеo4~ Nn7Lӄ[<ϭ.8 -^B {bd0g^/"Ul>Xl6+p&H]I[o4Bԉ9bQ}AAP(He*tz-`/x,?~O֡t)^xIyGv!JnK `2z}QGPZ\Vh4HsszƂsJ;> bзL<~  (T*y†NB,$yec)YLnp=xƲlĖZYr]1Cr|8C&Fd2AB&p8xӀ4l6䁙$yl^o~x~hfDÆB!Ml mCʱb4j.CZFF>h4&`:F<?? X pd]t]A+Ϟrk1^|)OV C!MFU#ş\?F 28]ά}!JYɤ"L ODDo>Z#ZM]e@X]QdFV+dYV+^P(X{m.K:h4#\lV^hpt:D2L`,x*ZOuUveN+^/_z͕bhMI7l2zY\hr80c4Gl?P}rQTUԞnkY i8ݻ!'#t"+$^SuQ`\]]իWz(_ӹ|]~khد/qnkvyyO?TGGGRNO6e'}~gggb2JVzi0 C2Gzq!t]"xOKjFbæb1y}i}z^lqJφlBn4ISևxR~L&nX,& Xa}5fct^oF?u~o~>N?<==E^jmѨ@$x<gz IDAT*ys^x!_]]r!Lʄ*֪+E@DR9 ARj6YSj[!t6?"6z<DQ Xp`7˟Cۅh4B$AReh4 z$x^U'lHim*ۆt@[S"6XRUUQılj=GT \ hTlEl2qJ gUU}T4UD|e1 L bCcm~l>3 M!wx$? }~گ7dT,0(Af0 3 ע!9ed$@VÝ;wquu%q!SjJ49%fBTҭHeݢ~_,@].e=孜q2?id2s6E$IteR|4.yAUUԮD&N2]t:z:t:|&1" ZNR_|) 5GK- O?#zJr)'=V J h4ZL&˅3CW#7TrJLjR$ (";N!gvïo3|Gv5O52=6ŢeYo`t:crxPr}l64MIIvņRn?Z v!ũ0y(Owf#|z` WXY(v&qV/OQ$fb>#c2Aףx9\.\X>J LD(J!l3V$3h=:n4Mmr{!Knj*v]ѠDub"Hk_1-NS<|JM3KaNH0U7EжEee(@ҋ66YDʽ$H$ BCՕqR)t]y>z= Jq[@J :ʴtбaÁf) A?u`R E˯鴋|ǽ>rM[F~~`KRrd6$lV+}}8N\,>={Mկ~;#J}ڛL&DV(~i"HPx\NGNVG4%f@gggfͦ2󖯟^'eJd3xYk0}feWWW"URh$p8 ]h4Tjzv(6˅X,&̂^}5C}I,WѨ|_~ѨUDQBaqKC<|_΄5Mxp||^ϲRG`0@"n6viFب4n4yf7z|( G:TGWBF#0ѿl<^ap߶|sl[to¹ -V2D*{fA&Aٔ^W)|z=kui]o `0p8 t 3,K/^/Z=QZIE08:Xl1%׽Z|OY*h|^Ҥ{<j^VI\4h4B2(RtR1P(du0\~vSO~?rlTDEQP.RRXp8۷o 0m d\.Z-b1>?-t:i_=pG"r9j+PO~g7>yD3Mݮ2N00qxppdjm q؊=lz3~y $dMχvYdl#<'ÁB S[B $c$ 4eoVxttZM^~4Nb(R)˫?d7Vki9׬ |>LSI'HRU҃\EQp)k#i3]ةf1d!GڬMt2EѐĂV%fwyGl"|q_n3fl6:FR XHNXAl(b1\\\€Md4* ar-KQ q,+گ7h _oַk߯a`& \.:klPxLn!$/x~Fz?T6q0!H &P«Wdp8,P($^TyKp8锉aTUŧ~j{k2_8=%`պ.^Fl6N %ױX HR? \r) EHO63Ced8x<;dzlvn7^|d2t*2ZN2yд@dž!9@o4Xh988z6E</>LӔM*BhaJ#/7 Ѩ^F4MDQL&ܻwϾzXT7/H$^jsτ/J!Hnۘ"'0ՅMg4;^ho>c6 dKtd2<%|>\.l&*#k}o|B/d˷n}\.իWgF)8Y&`W>8Na0IUaz D AUU G#V3#ˡx<~ff8:>wE| 8*..//EDjYfV=88l6C\`0 ~\~[mI%t:?C)iv>lprrx,֍}k9(r桒S}NYN>/_7rH|>  0 C&>}"UUl'd2|zK;0Q%TsN %nx<.ilaLSV+P_bd/xB|Xtfof#^ Y3%\831ӴmO9Viܺu ϟ?ùE9b6&TUeeV ȰlbQxBXV0MtZFf(0^aE, À|>=M?O<)j4vJu`æ R ^6Z~Wzb[p8xw$Dz" z,EQ#gGniH\Sý"Mn7h4 i.;x'ܾ{.Fc ɉvk[' LhTpX4gdE<i8??Oۅ+ Hfa*B}>dNPks8(G6 {w1pxp(ht:-{H6b4֭[x((˖붔2$5\ |>'?`d-`G1@,S ZD4MC&ARuC:F2-n|]o*~/R*|'4MRyrٌ𳓖6[z ;9 z>\.NNNPV9j{5/Ƅ_T/q_o7kЕNkEE ~wDDVKr)>5nj?RTB(BZE8F0~rooŋB&#7%k&-BXl6h&蜜|>5GQa3 L(C63ۺ.禗,ƭ\4M_xϞ=PLIߧilcL-Y4{^D"R)iRXqVfPAPnDQ it]۳gD mZ&ΰZfE6(((<-/_g_.-sblΉ:9Dgltt"!ݻr\q 3T&lf?t:Er[1ȶd2Fq{\uLkZ*{ l?!#Jfn6)R˥|6 dJə@Qppp QLc$6z0鯢?O7:! =c$8Nl[J%AQ 2GQ Dq@ hob!iϞ=C,LPVx^W' T\^JN߇ŧ~Cni[(qyy)Lۚ{m%AϞ=gpT* lb!w :z\."Itf>n߾{᭷ŅXxsK:5^|/_JCJnxSb4MC~%JKe5h!Ng=zF>L&^' rwL SAQ?_~>hw_}a ]xv4@ӑIfxCt:.s”H$h4l:Q1eA_p8X/֭[quueO]2dstp-r99E#d\.f0O`4a& DX:ukbӧOe),]L)jvtիWX.jg^VbK=ҵlp|r̡K$w:E3>ܹ-V׋/_Jc L8<<<{ sfS-fc;1 X,& 8&'->ךr,c jhg0|G4np=s?WTEag<Q PZDEN5R(«W8^z&vRL(@[zuul&4>O јPC9󧽂GM?RX CsZ-(Vt:j+XM̺R>Jj%1ĄXFBSeÀW]ժ䰭^C5zE/`:"!K)-zϕJ%t:T*dYaxlX,h`3P( JY)#6 F%i0QuL&B!Sx'ҏf&گo4~P8|>K3ZrpӉh4*vb(1n&TO!j%t]G fi"+N& E'W`I#WbrAցD" eQ,nA1(ywܱ]Ϸ^[rJvR6E,2i 0RbRqt]$I1l j[x4p$r'HO$X 0p8%Bn"^l6Qi~ Ϭ%VQDZ=)"뜘j5;6m(yfpyy)t&#N]QVQ$ydPUz;ߝͬ!nb$ɤU1nחͳ`0hy}>$I*n,/Zvz~NY ô&Na{bAP@݆iLH$( pݨVp\F  Ω۝NǸ@VX4N#p8,M &XJ!]T8!İQi=~6yOYrlbk6#MxPT"2j5dH%E7"lVb7h ʆ%U"Hggg`Buw Ál6 0 C8==NZEvkNcv= NOOaV^10ka5hFC@S6p80 ɔd@ޥn+xkZ.k }a ZbQsvB8M!P {FVq\.OfAB4 d2 l6υ?Xp8F!oZkl6h6NeC0yn(Ln0r91ϰA~65rK_j9EQ0I`nˤ-j-4vr`0Eݮ4I`\.H$=`D $k˅s˽JI|׃»+~_ $P) `[z.b5b{p8vbEi# ,Ff:}d v8"C8.z}r9l[[P. ϟ?|>GZe0_,Bvv|!>/^_a]r OZIS "=*9`sD^Brbx3pxx(֊h@2FA(B&.mŻ-m$w\^p8,־UUkfܯ7گ7hwLRd2R麎l6`0( P($喳LHvd/{o}^yxe&3IdJDJp({a6ծzҵ^uxzG(\T舢d|<]L=_땙?BA|}9H$q{{+ӤD"!,~cXrP(@QYc^#%,JI0j-9%H$h$OҶm6S@iٜqG_r(;w'''h6L&lFb r^/3 Q~_$̱g1KDŽ FRH$Kp}}#9lHW*,K,KI2_"a8"nnKc ~4AQ躎S$IryW/v+<}4~t:UFr%5 J\.˿M`#E|^O,. ~Lr. @@E>r!ooozT*riaTfB~tt$axLNx~:e6اn>ORLCXIUU>%eb#bnՊ3SX,j*pN#|T*j*E=^V/dX,v+ zh4* BQfbz"ߟvxvqj;#$ A5 m|>,[@<f)!F@Gi"ԔqۅӵV"Ϸlh4rrt]D"I_`dBO!9NfA$A,)K"ilD w?? ;j.ǃR$hzDi&nooDZn:yb1l6-߷^Ol3>jZt}~[گ%+L"76M)GAt]DQa#|>#tb20 h&}{1N1JIa]sAu#REQ G,7:@a</C;kA0ē'Opppkl[6 ِRKbؚSAj:#X(t:t:sِvpmLSt]x^) J{X,pyyV%d2A\Ņ`t- 1e#+~B0z4lZ-L-( aNShV ^t]aF*z<Ȼr Ӊx<.j1R-uAU 7p"h&5cQCO8=߶xvL`7NeBt,XYwP,q~~+鱸e2 9~_>HܝO jT^...si M48Att4{}h?c%B,z t:bgx<.@V6 m6>asl6$QVŚBH'm/6r5399ȥܝQa,(s\NX!Z-k|d2rz.4\n1}R) C3|*$0 ˡhH&4x45jD Q4Ob:Ƈ~(ZPTnV* " L&d2FJHߗlx<.@X^{fBx}b1^vcTn4e:E:€7˅X,0AAXԊ^'zcA@o~,-:4@<݅% u], n{'v߰8PEQH$pzz*q.+DMmqMҒa8Naٴv]Qf3yx<T*Թnj5^Lf[oXP_=ٳө J@'ՙLF|\&P/Bx'n~%$h]`o^wcݮLBvnwp:l68::!: ZvvT? ogyJM>Q7 ! GhD"!JsD.G*<(u'P6ϋ ,PyIJj$>cHZ¼p8RsQKi/ʧj%"Ձ@n[O<&RŅ"jjvFcݖ`UԞf M JJF: f~ۭ$]^^b6-֖qPvt:X,qz~c}|IUU$IuyW_~:wޑ$z-6<&9o0+l6iHӢ`S:vDZ2 H$Á>z\6^ۃl~Kt2xya{6AQPUZX,d2P(d2T*P($*}REjY2Q{gZ__U݅P(iټ{vk}a_:==x#N?!2.//H$L&FD0-}RI^0 $ !rcn 0Ѩ:HDLF`PGNӸ5{=~b2J0 "|>9ƆaHa^K`0at:`S UUEfi4'N@Gqm44 Kv|7!L4M)lrH' t="6MfSSu@oӹK&E8'w-6PXL 'u/{>x٬7!Zdu$)s2:LӔ›f3~"NL`x<nဪbA8' Jr j FU~yvD">뺎|Z&[ H #N!<o\Ch`S 0D"d TbP($ ,FJ2ޒꀓ8sJ@͍LoooEC黪D" lJ@ Á"P(5ȤvH$L&F!\Zt]GF Ԏ^'a:Ƌ/$^˵Jads864MC8F0(SxaC)r+hq0 |>oEV%%4MCۅa(vuDߏc}Q Lؐ^/_?Gگl _owlZrӧh48==g(i3,8F&Ls*Dm[|>@`!i@@mqnLN'64M)Xٔ8H$"l!)sφ=rY6hH&0 1, T*liB!\\\Yg4L)ᰐυ\.N7z\ `݊v^VtfhTmJ +,0A `Ris܂%Ia"P r7Z&qFEQp&|-cH?B`؊ۥBF@,1x9$zPMO2 ,J=ǟ{G"ߤBٔ`c<ĘѕV `P:,:\.?J bt:G*Q&ARb⚦E$pXlR }jPV\.|P-|>GZV "L|z>ǽϋӟoP 1MS,dBtJE )VU<|Pni&)( ^/0 pXb yl[|t:i4 /^x_P@V WKЬ 6`Td?ϣhvc4j\3";L&R)<{ i˗r]A I<.//z&"fTJ8XK61ɤ>#IQ* -+ln3uZ{v- V%>.1<4 =H$"FVb'jo?4Nww_}a-^B( 4M[YJdRUU__"jj! l:z˝ p ˡV ,yi"b4 !P(fŢnBFy8bnF888nooeCƆAPrt2GD1l6s7ߜ ' a7ppp ;J\2˅f3RQQ.a&a븺f(jB"h4t:a#`#w>GK ~_5ޣz^cfALӔzÁ`84M$ r9B!|>i쮉4$8[lV8NasC>Ln\.t|>c8 l/ҊdTd0N1NqOO\.?q^z a&v;HcCvŃH$LHM<-D& !ЦJ4ɿ:N?{ n[}{ߓ]~9 ,LϞ E2\Xt-KHmUQ|hFflI" n,N}i&9A&t:id\.vyP43t}v;4,OhTT.lv 1pxxۍF!Lzi%X,|q\"Xjz-MA6hkXo6t]QpkDs[y^<|@")뵨Phq^*JRR yb1n!kn 'd0DCU(YL~Q Mx+]~Ekhدz_*cϟ?Oo(oϟ?fp8D(ށ4XibxtN'\.6 v= i,D岀Yp\.d+x^)NT|>W$`0M>xe"P[.UUQ,n1ZP@<H8!*jd:l F*Zb0H9'Vʯ v@|>6 DLvVxJ5 IDATM{|O$^{6{өX(f3T*`0nG(B<Gۅk4Lc`T2dΧ^(>f"g mA_P`0nI(gU"Gy=sttk%|PPnO !|6qyy)ͦrTp\.' ZѨ< `ch>#L*pR*Z6rnؔZ.,`X,&:L/YV ߳Il610t/0鑮V`` }'''cˆ=3C4!PMT*%fX,"ˤ*>@$' ~b^?zF8;` v-E7=F~_݊ɓ'rlB?fÏ!8ѳN>w6[##Lb8 EY8F^J jN~?Ţ@ Bi=mT#1plB2hRhfӉϟKq^(:"f~_!|1v\.#͢hH *#+888Օ$$h&, FR) $³[D&ibl-fd!`<}<E=crqq!;^C<}c g7A߰3U0گ` _o*٬Ͻ^/|><2AZG?aVEkDQJ{<8NӚǏ4j/Vh4**Rй9ev vŇtd!|\]/NX,P(6S>|P&d,^/Cfa&NNNt0Ѱb(yrNR0Q*ii0 CB,{Fd8s\8<< 64MozF$A(#X2fAu>FGB=7Hv\a |$,/hEڽAgіH$,pcG<P3+.\2l;7?{P(ߴbhZ2͒+SU6z2k$1~f0VAuz崘3)pX lnr9Q#8NxXL4:RHkUTa|>l|n>c^A Ʃ:l,x9|>L !DL&p}}-jJ2 $^{rYING@xlfH5(wdՕ^8YoN #m|ᇰҀ$lWUU-{m2ngmLYl($Q?l6Z-nTUEf3Q;,d2x=^c>K#dZT H_*Dt~RE7گ` _oя~|>+'?Xdt:3m)V87Mp~\X,BzFN4)FC pX6#\NGT-b! s0  a6!b8b4!EKxcZBn8<|wRA;ۄ(V%.4MbHϟ?"j? > 'pԶ#ʦ|0XQn>9NݎJt*| 6X)۳l21%5&T*f<K0ͬc+g}_D"<|P%n[&P'2H #MK^OEyyz@ajl6r~8eӊ B!T*7ѨFQ)9lr(G7ׯ_#H hd2) {/Zt:p8͗sQb~E_> XL!34Gi=Kݮ?Ǹnnr9Rs pss#`0|>J"?N v%Znc>bㅊl&j:ruuߏ/888fm  43 cvw]Qxl0`{ eBMV 0N#2X,1b!6>Wm63BiYsV`0(J*G)Uub1ӛ}߷_7kv+INE͍LY0BHRR<3v["].!"<4kQBX,`LDfR*ПNf2|8<W`ffaC4͝d>G0k"B+NK"&T<zVL&z.+؜vxw3MPHBT vF0 G"P=*Z`w80Ma s$I,"5 Z,]:NM-ۉ^LD"Ҽ].STb F]8ŅX.؜ OxibWr:(J$u |$^<6] >rگl};~vzzPBJ `E2x\;#YjPUU6*qGz.`0($`ht:2Q4p++S4l6ah4P(vh˺ x >avxLԨb`Cd"id 0*nYX3A|j, 4M_OccD 0%U|3FJhxh4*͍x6͛o_O4^~ShTF466"E`0U'l}<Kl%ahW2L'''V"-g,'Xal녮 1C&0 WWW"ɎD" FH$R{ߺ׍UUX3f:dRbm6^~p8 -HreG%gCy9AN LlmDQkJ%i*l6*\fZz.VӉx<,5# YpdDrV^z%5Z0^.b6!?C0nL&E%t:p8*JX ,R9&Ÿ`R$G^LF@HDI+>OT-P-Gu㇗%r( 6 n72T'XLǷŢrܯ}a-ZO> R) MRRF#l[M4nR)y2yf3p2wEQH$4m`I?cLS >M]%0e!EAn7{8MҺZ"KN9e@nk( ݮl~?fUUU0y^Gz<pR0z=Dm6ssJpssD"!J 綀cqOyn/6`٬LV-0`3F0ė_~[ܮ%s]-[~|. R32}_EQM~B?Q*?}rʙ~?ݮRNSZ-,K\]]!Jݴ8::8HldrHL_2_uO,$4zLz2* XVzEAVÇ'ˆ|d#l6֑O vx9%X;'B:7FKd2-r µht3aJ/l w:<4^/R zHr\fwL"PU4:NȃX,veOv]\fSYR\>vEXh4N~.?fzu]. cnZa:BuI[ fw_ ]F~[ \,?4`0(S@_KE)(4b!R^N页zAH$`&|> d  Cб`l64PE%L&rzh;D"o4 DZ^~jx_x^ٿ/g驜gZn f#()T}1n|$K+NE4(ʂ*L6p8v-*1>p beÉ{FĞv?.Kl(2|pnC+J2d2A,CÇ1tyn(y}iل nńt:h4ZG{h4Cbr0-yS.Wq_еo4~%P(h@PjZ&rY|RQTq{{+(aB4@^h,CVtݢoY...0a]4a`wء4t{]>PEAPӰ.Eh8|6DDRF.h8-,:]c^^|J%Tm<mFVQ\.Z.a ZNlC `4+t l:ףHEu&<+ִUUw9\.D0 xz +=,l:é(p\ЂGR06\,ut]̦Sh NžN pgXwMx<OXgɓ'T*bYukf׍GEǏ# H Lia`0 4X͖V%\)02ĎQդ @5x<~,(Жi(2Np9]h5f2p9pp9]XKd2N^  ~_~] X ^~>|~?"7JDya.MLSl[4 3l{ ɠZ*6VnbcNX.M,3(Qb1\^^tBUU!Cv x<^hZPeͶSXJ FqT7V& T~EA.p4\ ix }sS]~}׾Ѱ_l!+{L&vmxrZ3t]j}W"/7 lB'?PU_~%.dnt:tP7R00SXLB& b̤~G;LDUUy( DQHdITߗH2~.jx9lL&^Rw:] G"H㥝x˥ Tm|O=/8>>=eGf@^t:E4h4 T"/3`\Rilpfbiot:X,0N`Dӹibf8ݥlLv;|.4N' ַ%KyL].r4&NXXXXaD, !F!$hf̲PUTh0M]V#ݮ' +3}SgF4)L^/JT+hF4t]ku!8Fx pr%<~/is.[r \Ht%v ^^PU?PTp}}-\ x"1N%`iٟ l6-Dbjފbf(|>j]ZDp||,Uh4$-uB+ y>nQTo´3iR]~}׾Ѱ_O}t@6D@>6M @') NǨV"4M&RIrclL"pEC*b졪(;'Qdacnp ˄. ĕ{XL,Y0^\4r9\]] .J4e2L9'8el65x^!l6)NB'ӧ$I2SI IDATD1֑P\.Ga0$fC B!t]LSf3aIf!NgmD-5M`hpXdX<^xR   CδPrv /?ۣO6/MӔ&-,)!4RFyC%'jU[XԒ0QVEtv슞+S0N-Ү@wVBhC*f߸J988@GXoLߟ^b4YAh4x2; /̐expss#1x\gRIaw95|a 4Z X>o>$cx<~ZTT ,̣GK; "z4h9K&x(l4E<gpNÁJ g6S~xÓO+}޷_} _o}7^7IfB<ׯᶸܰ  5}f|v̔>io3k4l&jiM' E)8Ud4M)& Hd2)"}DB9%d7PHRY-Z|_M0N%RTsXjS-PB!ٸiZ-~7byWZX,p~~.>F!Ipt:|Ex Zdal@dɺ傢(v6RYdm<~FCZM5 #aZj5o6 Áa;;;> E#sz˃zXaAd2fёl&?*u6l^[S .`W^IT. :$2uړAHuhA*"VFz.̓?? >Ѩqȗ1 CdlR]DjBF6`0t:ERft:C(GՂөH2 "eD$ ÐԘ`B͆3@*|>G0DV糬fvIPgݤÁ3de4p8t:$gcq>1NEMruu M~آѨ4;sRgu]1oR.kakhد>xpdb-d<>co[ih&`bχd,r:N =Hfn|>Xj*1vq:,XH> v H v 6lֻٱ@<7H.*F2N?6ax?#uII. FĔEzF<Λˆd20 )izgn.R0bYTrp}}p8,z\.'|ߖ4z tr^})W 83DX=~4 ~?ժ%&dFĎH$UU.rm_|vZ`ȳ$oX,0ߴpˢjh4{[*`0(` JA3vDQ(gF*;}\.4)^/J z2 :\|n0 !Rd^Ol|pp QxKʽJ ØbfAA(rulTU\.$ !fARarj`6 R,ϐ`EQs͆f)J TU87}d )DA L4w4f:;;C8p(ljT*`0(,n+6Lw0MH-˾3;as}}-ǂ.GGGVrP6KU^*X,0 Q*"^r&rnh  ;kDt~O-o?.. ^|ܯ}a B(Lt:U9$T>JTn#'J=v>Z iʤl&^ hZPXp:+2QEr9N1y>eL ll6r]dVYN?ݻjbr,SJH-`0 ̮ѣh t,+ Oe#.Ń\.R)9.̒O$|8??Gݖiih421N$Ԡe&'ɻsƔ@Re"j~(]t" njɓ'Ҁbc^ ;sQ8pM>{,|:]d0q~~^'Q wMKq^~_"|_|)эZME+M`SQ.XB.CLlN"FUU|'Rf2l;% Á(^|x<.6 jsIÇ㸽Cדf#@YڊrpZjRdF& /iBu8vb1XL'w~on?گo7k+ҡ8~̿B;i«z`.2 h, <7qveJBh4*|NU( 'OP(`8JC@-˜~{{+'LNG|ǜq oFqY(б&^Blnh rxb ^˴j6A4D"X,ZB0 %DZlJfXDZp8b;) ۭL~? <"(4Ma%pH`0@ѐẻDɰ+/ t-dup4I ZңL&%n??R;N#+֟:L8]u^_= O/鏩:P(`2=n+᥌mu],AF H=HYW*9\d|h[s2${c^#Ɉa࣏>wQqt:c(v+zYXj:D* NR Z r! ۍ@0pInܻ;|t~Ʀi-~Zt 6˥#l[uy*ѼP Jzv% 00hb1i6In\.E%;F Xߜ5 f#D. k&L6<{Ҩp9]2'~/i'dQ9S(q}}J|H:<: ?h4&x< V ÀFrQ*@p8u ֫F]׿:=jmw_ YF~7t ɂ~zvd2r\.eej7M;7" P@@& N ؑHL;S25R$Znx>::p8D ) NJk68<< *7S̉ rbNOOOvuLf0zO`z  B+ * 2l,}ۅ-":v;Ţx&(E@G*BRA228w Nǃ,$` yFۭLϩ8}9#+#XXK$H/H$^'lgŸwo={Vn7T*qSzv !}UTX.b HRVX.@.C#\k.|bGfr R:T T*aZ n[Qa9sk#!)&D?ñۺZ-w ;LyAd2P<>Ͽa4o|>Y!R#FUꥳ3yV7{ڋI`|dRLa*{L0MSZljz/RL]r̈́K=0 ~r9]*2v\`Rl+qu$1  UUi/{o$}^y>}ؗȈʬB!IH\TMDqL2F7έ[ϩG'N/-Zm!JdUeFƾ/>!˦{4j%\< BUU1E*b(061hT=`fSo$_#l&<ދNGsA5|s__o7k~EW"~(z0L04( WNn8E塔FɜHvmx^<0V+?|2Z`6HHJg r]t]GD6hjB(B O8['OEa|>!sr0aa&c'jpm0wx? AUU4 a`~XHݻ'ZA"x`%kXbQIZ JTT* 菐8Պ@ l& ]h4t:eɽ\.ZDI=n[x,td2d2v-6 r-Fxi8Fݖga F"?kh"0ҿDP(]%Z++TNZMOJn ˅v 037NCua^gd.y' t]$ io:&> /@ ԃcZVa:/* XxBeߔn{h3PV NG~Z{Uaw^ @YBk0&c"I͍>/~ h&T)V%RݓEڜ,N&t] Xg; 1Qb,ܙBO>\.H$"@bW^I\f/^cZa6! ! Ti\"#!Ɉ:ŐjEVF8bM3vlgX 2frEBVI:,ۭBۿ߻6JG#Ol6CPUO !i$?dͨX,&JGa<T*u>ۓZ&*,'?vfX̴@<X=H_ nG>.WͦLHDlAQX-k1۝rZz$>|+q^(hg5ZM{I;E,eAVU,tZNG \26h;%L?Oem c2w6Nۭ0ewM nv .[a>r븹ヒP(~Cd2)E^l6fV@ 0PN/%svNǟl6It])X0lzEp85)^| ׋l6+z "d;L&B49}k_zƋ/0 0pǥ@yPvj\.%m90$ۼV KJI/ "- 0ai6... qyy `>p8K&fO~"|r0=:~?v;X,l~!>x9M$&n*gbݮDѻ>N<%1UUpݘr_ժgy'O{XV+I`H$ )Qn IX,888&fb ƙ2VP(˅Cd2F#?ҁ X )U2Á|>/` R0qEQTUAi0qF04#-w!‘| E|<h/\p(U?~F}ף Rd3!H(dpܶQ@ O#aC*$6yXkHՋfu 6R$ۍ~tX,fyV9N>DQ4 aA<~rYlfR)V+4MR Jj涪?1^ql6d24MIa,.+$Ėhcc.l|c#OT IDAT grX$Ѩ0Z5^{hq_o7k~ַ-U׵F۾f/RR V/^G} UUQ*dYJ^ Hf8ؔߎr,uƮq{sI.N#c*s;L01%^rj^\!6b@ O =qӟHIpz {٬2u]1=z]~7JM٦iXRZ~?nnn^u /`0r)St ;p\TǘΦXoְ;0(p{PC*NvSsbGTrI=c, &y-?~X,O?Tz]JtZhd/_D$A˼A( #l&SRÓpjn>֙iqݒ``7#n Jn15mzN^gW!wV r R|r ]F~ׯJߵZ_ 2%;v#L%^r@ bZK_b()B?Eη xfS"Hyx< e<3`V fSR(8M:D6ETj!l6H$Rm6x)GQ$IB!Db<|*rdo;Fx<y>O঳(q!c  ʄn Gvfmb>#L>TU+ 9!rY&VKܛ,,Xv]d2ߩ `0jF! L&N#SZ0HaJf߻CuI t 6UUw<3.RG:pl"}t~w + Og6l6[*N'ժN].BFᰨ 0h40N$54} B!ҜRB8 S"$TEc4AwOqZ[`N^1%0:jvd2ߓXc;x--6Ten5!Z+GRbD"UX,7x UUl6F@(H~d*%jRJł_.Vkگ7t _"+W~|^R 1N3bn['ʬtn>O\] aU hTfͥ,~_6lq.S;NG"Ʀf%qa NtF Tz0 Xm6z=x^tnKy 1hdgV8<{S|Fd"rC<۵Z- C[4MbQ2 d"uՊf! APFC;GGDNN(g8::2EJ^jDD65O$/}Ic`S^l.QZ/(K?~R> dCͦ$‰y," x<3.C,)",X6©3ዼirH4cB.lbV+d2aPCFc8%)Gvųgx[lb.f͆r{x RIU2 zC~v;n.sIF8ȝ)Ma#E|׃h4f?}?/__7k~EV2~(z@o1}NR(JX.hZfxco"Lٳgrpf0 CaR)- 6M<,jaלq9:N")o6(˒d(6%#{D!rGᅬ;EnuLRV) Ux~Hܚ |v#LN X+ l# nC1syvrB_0cBL={&h!)œRI4COS!O_հ^EgdRV3BT4x<FV+(f1N}NhAY.g?;Dv{ 7c8Yl1 $Aj}>Nxd]H4MHV*Ui|Npmd~ Hw5y%f aD$zdDjRxO5nx`G#y߰AldZ,|_bwN1w-,wJ+v<t]GەbyѨDɆB! nww_}aW`}_up80TWWW2㴑@0`)lǨT*x<l6HR;)t*Ӊt:-\R2p{2E&T)^W~~NoLϡi* b($'? ,>Sx^rךPHkv vttW^Vn|>?,+0NOOe"Cd 0p`bx}&o ɓ'P\~_(2r䁼^#JnGq{` ZnvǃlddӅ{{|vDqNayeA/ll6k ~Xm68MD\F:٥FB^w8`d"`0(K}lVUse:vѧ6U+j!sxz-U8+ l5BSvC2:}7I5 `z!R3}6Fp:JRRpC}FX- b1;n`0@A:F8b%Ru)`Vs f)'D6\9D"7P,V֛G7ׅwOڇN?lYHfm l0zaaa1aa2lPRBp8,RET0fTlj{X.Lt^S0H]pQ,0  0nEXTkU)4Mt{=8]Nn+Mb[~_TTp8X:Vv65f)D;6X(l6#8-hڮ˽>p}V]bt M@ iX,x|_+ 8lIܿs5~~khدXdn/ vRUf(X,$! Es^_L ?2ݮ6锩x<%x< F⫭27D\.fʈ,EDQo.>pQ;L*Zϟߏj<jA4i-jP*DnEN߲W|>j*H$"9ɠIjE@(CZ 3)nHR/~!֑@ (   |^%т/^`<ɴm`<חcr9rFnzzcw_}a _ܻߞt$kmeũvY'Z\.KLe0dHy_,B;%3VPq;m"5FPU>ZMH(Ӥf EH8}> 7%)\|>LeR6F4&X 8V%EizOfC\FRY.!b^/_p8,F!j./ތ N`n[߾oncѣG8;;f8$ʆa`<J`Nk;$cB![7)Zb8" -%#>B_$|]G?_.Y8?' @^B~_b - 6@fѨg $^Wsz?L+y;fD^'FFxP*У֝j*N' "|ݎYШ&tbVAF#s#<[Q ͮB&}գh.KZ-Zbt:H&4NS(`"h3VN#љZI~k ~T*Q~b03nn# c:FlQxPT`Z4ID_lz`â(L%d\g/ VZ*0:Ţ(BvVXs@f my. ggg(( PUFCGz<J%yPcZq=td2byp\ݿJ=fJmw_}a ^lV=99+]]JL7-ݮXHzFbXa[NKnRto[)(ovD\.dǢf!ʁ~x ׋fh4 %NQklch4 ? C"f|>/xy~Ƿ.ro6~) cr5-߇iL&tKjUl 3SSs6eTXV9<ɟ^z J&J`69v3ٿL0 _rnxx>g_' 4M`03?0K<=x^wyz5*!A_@GGGG5i8 -,wqq!3tЀ ].Ht:X׈FzP,;%HX9P\NLxzC>t:W^rIzSNZ{6$E|>GjB$v&Lv8~&l%/%h`6!*L&#qhTt)J[,fu?Ec';ok뿿گ7x}O;ϿN)p8@VCӑb @hzJ韦>ߏX,&SaӜXZ-R>(L9avvN}0V (g0 XV  rHħөHH$T E~$k2B}dݎ+9OSI 0 t0!ϋ_`WRin3#R]qɨL(IlPnax(t]l6`ܠC$l6쑍d>>xjU<8rM(D"̂}˨(X,YqZ.?]߷O?gJKjE$AلG6ӌ%P@ #$|ip777FbZګl6 EQ`1csE#\.{ I;cE|b*:4qAu1uol" 6\.(nXu:躎|. |>?vla}~}]]rf,ǏDpyyp8,~ 0"v)d2WWb 0 C/^p(p( >fvS>WvhV>V%)1777H%S,X,xn74 կ"|@a1_#L'Gc.8֌Vzz4_Vn6 X8>>(h6L$p\x܃P͕堪*ŢYJN0puuz !.i1\.rMYF~4Mё b|>1R^|) $S>Nncw1p6E9FQ.2iLdR*&Yh(F ' ؜NR#i;mQF#;Ex<$ lx=~X (˷{?:0\.fRʳr{*00H%N R}. `P$D 3݂ Ti&*UUEO!Z4[܋|t:ؿyJq_o7k7BH]~Du:񚪪t* )̘QUVDZ x\ c:JFC8b\ k_nnntp8dƘ*ł}kiCFtD ݳGoVoH) >O&6 恘m|>8@>Bdi Rr>͍L(Eu]y`%JmZ 4s$dV\..°K:"0w0  ?Oa駟"݃?;#f)Ske[,1n ͆x<+r#4hopm6|>.%޴ܾ܏^W'רR\NϹb8hH<# m|>vggg=( ޺ l6q (Fi48::DY[B,C4gN 'VK003^^h4*EvrDR`0@VmPL&nfHJ$l68<ͦ&fy؄lH$h6L&?f,|o0hZNx$Ph4D <f}ǟcc$h  fajzbtܿyo.kހo4~_?zj9@& Ò201\,yDSYо;$'D]quu%iBKeB>nWjng?`ʤz\n# R5`0rD4E@:Ԝ;^PÇ%C3jT* T* xxx($|HÇH$\. ;1P3gn^BA kWn2$[L95bvfyzvbv2 3y:ͦ+"1m,\b HAχkݎT*l6++ s:lTܫRՠBnzz<hs5Ml[$I, 4 ɞV^Cb(HRX,X,FBPU5d:EB ~e8NQ("Q9 t:b "ĒV^Ol ,TV+ȉ=-TJP= l6u27 mZ%M ctmJ/s]W w䇺pnd"Mݖf 2:8J~?ki$ln@ixZM7lV`?xnݯگ7kxlGL.=$ yrtprr"MFY&8vJrm/`WWWFp\BgdxX."53Tk<>ޫdnc^ pBu|a\FFNSl60pTU`b:J,˗/,ʔG nxX ^K1-&8d`0ʸjv] `AKHR{q~~.jVU"nVrZtn74M #Ey(۝}~/q _ofj<zx88|v6xK& t|GKf3*7|^XT'p:nh"Hg#nKZDB'''21@>d 7G(ll޽{ WxpX X.Z~x7.'Ot::? 3\p81wB^WЄf3Q܆<[jҼf25I(ˌ8I1p\`S|q\*u74MC4땘Y*5( X,;EBΈJ-IԚ&g cJ٬1(w9O˕OGONjvf)Jrd:y\PF'YvE}Cj|x,ϧc;D$MFx->LF-rbac0z-PD*.:<s;h6 03lXapuuǃ^'V7^#~m{h?/'0mH&eCa6IÔ v+qdBhNt*pX (N~n _o |u\_l62ab`1KTᐂ Ih~'zv-p&av82$4 85t:8;;$hӇJټE k LTMF#$dX5'ԌOdEe{H bA,8889o[pszim+AoSmF%@^O}2zя~vxܵlppp 6\HRߔ΢FIt]__|7uK 2l`po?0DL)vezX,&m,Ji'~X.D"(aynd2s:F2SUU MӰlpvvFehT700NX,rnwﴁuuU(~X,pJER Z-:`&$|ZoA+\6qnqyy 'Q׋N EϋrYdrY"'}$"^J"O| ̼gGhs85P(rIp8D6Eە _~|>l0~" 0__..en4Zگ_7kސf@@ ,z5M9)ژD"!X,+rVSI; ݎH$"dB "dQ&,n7Hr)VrJɴ1J(ī+d2!SNSFрjI)8 {t]G2D"0b`l6_ut]!v )9]VѾ^SNbQ|2,5@򠯪*db>C4u*-DR}L&vizajJ1Gj{vbFq$ *Zz{4IH zT$  Mv6jԒX,`j* BY٨ᄜ~G٬fOOGw0/X,%8;;C$cP%ZE<6MT%4!lqm(^z 3kO$iX,|裏ZR`<vc6#{|jt6G>|t:f|p=fP,ވQjU9(JMu:b;XVZC/lҮ646[vn( t]׬)mkj?|_UCQ(q_o4~!+}bQ9L@||cR PRIr? `1qzq<A(R,r>L$Nul6\__KNy zpT*% 1BlJ"` ]6 ^OZ&nߥ0MF$A|=Mo0ǃgϞɄr!uąLR9 XzBq?::ul6)n?Ō bU 1>]:k9NKhZV;bB;'&&sFnBI4R)4MNe=S>~~ HDyJnS8>F(ׯDD~Uɹmۘ:>&q- B~.|^y>GinlYVξkeL&}뜴2U5NF0Mggg"&|ln> ״O>ZW?fÅdx$r=z+e)-P,W,NwbF,cm^6%g8̞&?F$Aل¤ u>^p^ ,K~ Lea#qlB`J\F:`0@ t:ٙ$&|`y&~FC*! ;`2Ƚ֖yݻ~ŋu^_o4~VP(YF IDATr< 㑉t:vE9@Ôh-­[FEz[Q"TJN:6=tZ|ӻoY~3JibLEQ dR"xxtZt94 neN84{ƍlRsa&IG#4 4M"M` tc*cܼyS`H, B!z >B8.v&~V b4prr~/J] 7nva۶i,: E B F}6n]Jp"9??ɉ䤟I)\u{xf@usu]k6ppp b(n⛿b!SNMpqqh4*}zci6ͣ,>JD^c8"ʄt*;/izYrz @(PbN(}dsܿ|HDT/l;l.Kz߇ix|7$9#(~HA( 鴣s~2pZ˯k`\nENPB 'Ĵr}! __ECxG^t$,(J⡪ 6TzeEƉ6=88piv\.ߏ^|>/̏d2r)eL]Dlj&>_\C vn[uJϥ:^~Kx<|g*?翪O?}~5}aK^|GO$*4V4JH.ahhZBo5cG i;rs<>>F^֑ÙV_V%*^lWM򺼼i0 DNTJT<JO4XT*%`͉[.;GuEkb8cc2a\ra\ hj5y X,PlgNQނ4ڶ3$ Q Cl f3tWQugc:j:|rrZh [`0zE͛lE6N0!%T$0ɓ'z"%4pffܹ۶QeR<\\  vAudF=/ q)`XEbс=~Ѹ~x2IG"f&Iif1bE5޾}[V%DzrqqS>A}2`vyń\Nm^#xN#z)5n޼ Rqb1z=Qe`&:xΫ*n7 Un].L']p:Eαau3g;f||"\vPq_׸گ/q=}4[lV\.t*rl_4 BA&骪bݢP(@uBUU恲V8j!D~ڶ-p8 0i %FnW4 rnJ>/Q`7餄s6A4|888(a$ nh7D"kFL@ ۍ\.'N`0vR)DQQT`qq^ M 9MjP%vOuiZPjY)vp%D~n$0)N& HR 6(8`XlVl \\ cSΝ;F#!_-&2r/Ϯ*~ƍy?].MME$4M{kC9cNa-hTXHh4qxB~FF2a888=L렝E*_% 0?өHI;\-@H6Z&%]l6FZEȼW^ɴ޻` &Aض-6RBiiaL"=fx\mgOX,Cܽ{fS['Nʃ*md3s6^/Rj84z<:SU =2GZ*.ro`kχSh& Z9-!8n4Au $vHSZ |raЃNy>7 PmBᄁ(x4x; ir9\\\i@C/GI:'|7N#He翶,q_׸گ/i=~|r 2O> {T~?8^.//JcmdYb1D"z=9^{z^墯^B:h8i pz-~H"IC???GG")ٙ9ed^\N9 "m\x,^PH@}=  di, $}9ٶ-qa@&@hFC I~^,fy, ѣG, H_4Ml[BrJKL&q~~ Fm6,/..ddvxD& mFV.M&͟(Bh4* YX3`ZIDi:R`0N#<0t:$T ; 蜜v]sEQvTݼyJET\jQ|>@ ׯ_H Bp7t:Ǹ@&A+U:#iY]Ucji<eTJVK!>(Rb6 F!I5׳^{<cjWB91i4M)h4, kQOz=a;0rn#H5).wbדP׋J"%, k²,Q8!,NժX\bt]6!*x<r9f3 Ƃ}5v#{  䓰8l@D"Nܯo4~}I뽯x|4M?<P(|>n χR$f%H)WV۶D σnT׾5~t]cD1|>/9$E(+9\LFMaobXaz|ll05\nL3zɔH}>LONjE;#L&z.9dPvDX \.9 f3b1ض~wʄmYz=$!NK(BS I7M~_"A{MX{LZ$N_WĺtH$ӌ躯b1Y=n5`J`j,}>VݮLS^x!vT*˲lp|| ۶ NJ=1=IŦ|#uh& "9f͢C}Iv2q\h4nlB-^W>+Ӝ (p^K%τaDFU ,Ҁ]I0b1iaF0a\~fd*93y:Y# be4'!}i"H J^*$¶m$Ib1SJI4!펵iK"b ~c~fe2^*t]ðlKv;u N n @Eqccei9(L"f2}@UU4 R)$IAyΪ*M]84NGZwgݠׯf@ q]~]7kuiKFV!MN.I恝u|RP%H`0(ӈPv5*rvZ.r`YzH8GS}6$iKgD4'v[!nܸ!~v62ib|]")J3'VHP_~-뵨޽j~/VvL{v *lۖ# #s'sTj'w|׋<\@f^=B"ܑN3~$@nMP*-0q&fM;wrPa&&NWUb1Pr\/vd2KSx<٣6f8QI))^TRl^cQ&HIKt:(aD{i1ɤL7 4M='>O!R:4N~?߿/O d# ds@bng j%bgJpX`atݨj"elZ;{=Ѩ(ol6aYX=ΤuJdRNl6NG UYNꙖH$Upc^L`_izb35?l6_X,~YTu~{]F~חTMJ뺎\.CQ$)!ݤ1TTJbQV LF<͓D&2r#Tg!kv)at2`<Kn;5a;Bl@"Ae2@Z~_ PKj5ȷZ- nݒ)( D؜ ׿uu]|H$"V~(mwL3!s8# >|7oիWvD"@ L&# eߜc8N RJRo9qoZkT=kӿ^vL&Z$^ɉu{hZB(ˢ`qUq/o~{O<.zc9kEzx0TUǭ[s(N" t ±P#, d2\\\ȟed- 挪(  PڜE0ǃsx<}%AKݻwh4NGB~6ŋ/vx\$drوv܇N\*t+8b4ͷ&~bQqzIw(FX<KX:ٿ(u6 nݺ%p0D*ɉܿ_b([ ~w| Ã* N-˂ihrX,8<<»ԟܫsNSyFF۶ }p;m#Nnc4ǘL&PNwAZvE*OaNɤLi(${',<| QexmF#cx$r2EM$rh*:yFR>WJUU@$ iJ:idQQn#ʃ & 8 ={zD"^s[KK}|bHt0 pttSh(x|>\y~똅>(n+*n?q׮iպMOqG&9VIaJ`em˾$/HHfX H%gRl%rhFiżPϗx5S=X,vITP( ZD"?I*fA`0k `0P($E8lvqq! LxjrjZ򞏏e_,xLa$A4(rO"j'3x |3*vWZʺ[PHhT1@ mu{b짦ihZt'x43z]{^hTSK1"W)L>@EG*/?}g>r ZF~5$J}rHJ9yD"2f4RB!A|F<{nݒ2˲mX8 MSJϛ&bzݻ'lN5]Od2GL[pX^WZ /=P(C!`cŔNE-噄LIH$DO(#TU6@lZIAbyN:0 ztZz򕯠n#͛7Ryc^ƍ2# j!YKUWiN BH$b)T*qÏF#C5c@MDё ~OnƵM3S qڅ{qhtLܹ\.d"ƣ[FUUdL&1jDjNp8&im[g0`Ņ=4Mx<. ,!2 F0,˒ ;99A ͛7_ @dR,ңѨ4MgGNS;RmQy|[Bѐ zxbbXVa:"HQ$ eP~tI٫Wkݗ gLi[x<(J( , rDB&XZ j#GR tZFv[@?%3Zڈ$ϧBSχx<. 1Fz<zJrvhZo%'v،m[/ TJ`1=LQ @Z `yk=?tw_}a֓'OŅ@(frx@%%$Xm4MD~[.̗R4t:(m9z<})z"H)Q?1y)ir9'p=yv|>4M9g2eb19SV9L',d߹s/_p:ƛ7odJ DƙNE @&Cd"*Nqn%ZOZfјF@zO #2 Ϣʽ2apmPH$'xS!,˒T6W`@H c\,p8D(k}2@Qd EQ0sQT1zso"nzgp8,J|b(:dL&$ ]0/cL&HUmo_'w>r XF~5TQی Fwydĉ8ҰY CL&ض-Yaeh&vZ&p|r,hi C N^/Nڙ Ņj0MjU&gib/B,Xy}N8D5ˉԔ z9 GcfHQZ`YLl Xj0hH$`0O>j7Of4t=d22Vt&FfyJAAӁeY( cψp 2 H$b`aZprrp(m}0V<|4q֭]h{*U 6P\c{Q)pxx(m{\Ǘ}~گkX''ŧ/a5Qqyy)9|sK1NSi<'IZ-DQscrR PH)&c[(x=2i[h۰}KElu,W+~.V|1G0 119^< KN!?~,n۶qX( w^#" n BddPnl7T_)Sn.TJ>?MӔ4} Rʍy :@04M Qxt5ςhB m0 9gY- W`^q=ܸqr|OjUd 4Gq1}ݝ;(yrW~qW.prrjCC`(N:ϛVa#¶mi8)tѨףѨ\jcF8p8!y#RXV,áxAe@>uNFCC؄a˶m ,"B, < z0vP^BU}01x< -&H]#"Jp8=in6T^inܸ!?`_l4ZZكPl, r9ZM|>hF0`Pp-ztpxx(^8j%G&01tw}8>>_%E8ɯVPVV.l11l>z A_ nlۖ*3@PǃST*oŃڶ #oH$Yy~{XF~5,Eq<ͦ#H)w9횦1`Z|2MVUU y4 D$BśOL7 ҴϧQJDB&q%!4q^c\nc2^x^zp8O?T@ַX(J[,K]] T*f3~sT*/K9[D_>^"Hd]<i}!tt:-Sd~7 <FC&mNi7#v U|4h4$KC@jE? Bw@^eE:aKGFrjAHd2i:T_iOS|>, iy^aF D"D"$ZYH8##Llr9nc16P(`2H@`o޼EI?]"HhfϩHNw,`iq\gn7L3QUftZ5y>gg}~o4~}__9{ƳQ&jPVld6χN^'9dpR8=B ^w'lǵmmqiDڬuf6w&i"䁶X,b>hD6I N;rn P($q}_,n4,EQTniG (ˤl9vH$F2q^רT*rp8F]<˗/Eo[^d-a  \NG&XLOl*G8%hHE4,'f bXl?U_${k@X~l>EoA퀮_<`FT ZxXXL/rlfS,. z]x7AXHf˅xUUd4y0rg(kTb1*L ux/cl6f S<ZMlgnB?4ٯگ#khدENG_ǜ(5AG4EXD^ l6'9v"͛"s&Ќ>I<>>ƿ˿ȁnxqHt:8GMBd3ZMUUT*b ,KL&D͑"j5|>xLxYt$ ɴF5Md3L²,.0ja/^@4ۉ48l{$y%KLYgLu]G$j?NqD>ϖxSVk|U1/teZZDi@ \,l`Rl[@ dȦFX((J(kOd0_pX,a!LV>D"\id#ZZd]AYaX-Uo 2X-pl6rOc>/ qvv&֬jۍb֔|^i匄u`:JnbYc<`2@5(>/fә08(( .ƣB0oNNN+i뺳0 R$t]C6wcI$SOg3,dmx7~yO>NOد־Ѱ_u7$UUDLE蔝SL&\}^/48!^K h4rs䮓[SJ  B)F8<<#:S#d:Y캮ag6 TLu$ 7OSeB_NmR@H~/ݶm~q4M*}ߔ@J7H]Nk8NyM${H-B2DY; \ fΡeYDٔIwV|>%IeX,"" X`P&˶mc>#|?DldO' R) J}l6LĒL_5_mG$[G"\.ܹsGvh{*ۀQi"( PZM&Ll6!x/_$ ‰5DOiGqk/sòǣ x\. v{ggg;b!6^/^/R٬@sȉwF6J,c (JvouPcYrPY`zioaÇY=a{@A6j#`1K{g^oǁ9>>FF^˗/`Y4jCݻ wZRT*%t p06|H$?y?O:^:گokhدV8-* l{|K 8<<鼦id2)dy›)T IDATDc9;90w`&F LxcXDB$RI$aR_GGG 2Z7HRv tl6+jX!zO///EL&vի"^plVlLFǒD0LD;3! T*X,PUUH'''" f8??+?ʸMIUy0akC!k߃if2e?;;JN)#G"9 M۵Z RI@"j/߿I~7 H;3ʒioa:`>#Nò,t]i0L&Wo+)6~b?>O|šP^OdTJlwŽ^x(Jy; l|тqExBz]b[X 9l6E2(YN&evo޼<{LaRHD*X v BBg,u]IyyDyF< DB$;5p$,6>gϞIɄ~ѣի㓏<ǔ~Bl7ue!!#O?"1lyx4"C[AT)A;e!ld`Y$5, Nf<E"Ҋbz|`:V0bZ0Ėi.// ~FCx^#s{z`hKS!cqӑN#t]Z{4 /d~??v{~5}aK\zWt;z~2NEX ~@^|D"!v%|h3BJ cɦC8cr9TEJ%kaI 2=*V$Zbxx`.4Mi"C*)%q̸2 5G塖MfГ4˅h>LF"Tt|.KCK$Rt]ATU Q.L^@Hcd2.N4Qɤ|-K9Avtt ϱ Áp@ ^~z۟䝞*OY:L@aJ|LT@YFs"aۻECZCuIYFB$E?璘q-DQ~aHÇQ. N2xSI,T˲Őfw2jG­0U-8et\o"ߥ\.vv8&E\.uFw]Q~7. dP(CyoZMRG"u))w='yZ2B*-*FFݮ"@b@E߲$=h`ZȽMbz۷oc~Y[VE9 PQE vQo4乛H$Jzx4-v iCD S"6Ƚl6Zqlx?\_} __j4vEI״Ww UQVfLjD"2M2}`Zf4tPVevB 0F{x #Ͷ, e>xUZd21ezuzOQц~_&I|>i{<92򋶃Flv`۶H1I_G{/LCh&۷oc0s\N J"Df8??x`Fxw F%^L ^xZ&1~NGԐr^'Hi 7Rp|6# J1zE_(P*(7ky:NV+iF;iWAs\܃.@P/Fb<M{ Gaq8}Ě++Į*AFd2FțL9橩tkl}uja43$E2*32}c_>FJ62)oF*cl# d~,gd*fqqq!QxYs㣏>z\VP( JBA d!PyxFs4AhTDL?0>#㾴Zpxx(X,&EVZV+:\sג.PsR)|j^ 3-5㱑~q9aS\*aY:yX]U٣Ro'9)wx=٫xœsN)PCX((Jl6"evhugnŎ&iQO# "jFvyPTa\nx<^CI899fc(j<ȭab&AP0fl.{K=Z|X,dv7} [$bШ7NOϣ~S>VX0M. L H$"j.>?9 ɨ^|.SM+6z@IfZMf6vNWnn ggg"Cg "QJh*Pvʷ\.eB?x*S݃@`Lrv(J jv+_\^^bX`.K&̪ VR. TUw(V-@B͆@ \.'2n* |HRx)t]yt:UQh&h4RwJl! x飷t:G;U3"͜S_={v&_/` @0Sn͆l(J8c2bXH<`YtE0Lr$^fs* N'ZbVj:ĝN4oF3HA73G0р<*>$* 4Mplx?.X,&@&| } F\.rdO&f^Xh6@`m0T\.8:T`+SitGJmbT*u]׃Lz{x>*IeXaِfjVǥHeSr0`4ɤ> tIt/Ny$ Tڦ߿h4|>/65˅h4*@YZ2&nj"u{> Nwb<K{rD1Nk RhX뺙c}r9#W7%~˥aلF29)\_SUϾh4nۭڭvڭ_U.W߿/^\̭V#NNEA:Ə~#)V}"XV\^^x@@|^c8 Nޙ[iy(x!^xUUh4[Bxx|((1M!:<LiY (|趯_[n{TUx89U=88@$fý{$cjEvae_ \-dҿ^/ f OXdR,VHj8F^iҭkr9T*b1FMM^j$d2`ooR2o<d2A4ŧ~*wZLaXiĕn6B!Q0aXa6n͆ʓzyv}]w &ɗi3v)6.іLO`I0$f_l6NR\H0)X QV1ĚF!'O ND4#:P X,qaN|>+i j5ou@*#ypp\[x9n74lvElµmr/VUx^+eahXԀ,~_ŋ/pqq!NgRm_cvkkhح%^Rn2b|3N,U">Z8Uv:8>>Օg&b!qzp))!lBuf3E9,A\.e(g6 6")NXQO? L&Z&4F!_NOnr% k}Zb슇Zz.^uw\.eNEop8T*&1KT*|GH_B^p8Dڔvvpp =Nөn7BH|y8&un[@kۃE:ρ>zd2)y ^/`>!Oƭv\__ F'?n:{뭷P?m[g,*S"Q!s]dN6?~Ϟ=i899$>B,^6' H$χ`0^'`0L&T*%M3R ^WVKޜ뺎b("+d{~J| L [VzH&(p\HӸp`0]:>d$0;X2eBQIχp8 ˅` ͆H$",@T,UUl"_K&@UU$Ib11 WF3͓DוJm˫'''$pJ㢂V =6DِDٳg\`OeCV*LhD"j4M6Msן'NHRllzRdi/~hT,H x\DӉnpFa\K7 ,|b!>N IXfEV5Q:UiV+LtcoX/} ?@6?}wfW>">[_Fn֯T*޽7np O:99`#4dbFt|>͆h$_˥ͥR)躎kvC?-ǃX,&H%~b&??ɠV̳l9J!/c%ȵ dž׌mdAKiz,C$ёDRNt:E0jy.zT*b(CsBd.CXP(t:-f#={&.UUE̯a믿?D)0 B֌j "'!t* ~?EA0DXDh4BziJȌ{&<Cw:PԊ|y!Nhp8d2A:FPrVtV h4b!k9Xo[{ K奼_D.K,N٬@Y sN TvƳL&垌br!˩`1lJbJP#!7nl[Z-҂7Y"Ϣ^jϜfl"4E+,BNK\jfAR)F6ƈD"l6v;۶Es5Z ^yIFL&/}.t:rǸs玼L&2g&'&)?>>6`&G1- 3Nh<(rFOuX~C[zLl:" 5X >{D"`bp8,]܇^ORzn {{asԯ?/"yvk׮Ѱ[+}w\.|>*e;gpnhrb!Hm۱\.1e:x<$D?y0Y&v-khwpKN zbTrDZD:ͤrne? EQv1`ZeDo@RA>G2D/l6vCRrEA:0, >JA4*#l6жL$dl6c˃ϩӉDKSl6x<)+Z-)h"n7^{5)w!0.v\r'И/붯;wr8Bg!Ln]\.)or:1et:gX,$^h@u׆??HJkxh4`ِNnz*H$X,f) #}k( uVKl &):\._8N)].zRcl[l6)v;HC|NQɼXVb]FC#DF|"" IFEPuh J$H& T*bH&B<w,'IqϛֻkZtl*ZB($Lm8e6sjj t`0( d!3'ڭVK5 r]__ _#D`{&pfKE[dp.Su* Pf@XzQ*$J1MMCUVT*  /~T*X,v" 5$-kQ.v{~ڭvڭ_U7Ο>}CMon6bWEvXFb|![^O,&áפƳ_V"h  Dp:̔Mm8 E( H&7n@|$GVv/W*yX\^^—# IDAT4>v+d|p8 EW0D"@:)0!}%ÇBݖzUUe^ETJw~sQEbk%# {"Ig'8ϣZח PNx˗ Y,gTl6%FAi!qhZiT^.,#\.VC-#ϋttG@bC𩪪$% JM[H+㏍CRFRͽ11d$z&`L&cXjJ\X,&mŔ߻w^W h4*jpt&x %~)-߿/ͻrKSI&|>4"`8Jth4*̆}3o>#ٸeUC2|fX, ۿ ziGPUz](hX,IIमنB!XVx<i|g >x^iB!nWі!L`J& {T~LX)c Ln !J/By-V/h @p,qDII27\Nl8B, Z15'Z"41޽+$s~.7'ȗB {EUCVC.t$CP"0&o.K$Sr]s<{L pevD"%;NDQt]V+8"Adpy;}5r1M (N#)LFf2M'>`X0N4^N3O1N0NJ&QzFR1 7a6Fd^T cS("NK d2fbtBLIgxzpppp(r ǃH8r YrcrE?!T6}y;h!<^/6슂^f|ۍf6,@fz64͆tvݻ\ x), Q(Blc1L͂n'ȵ ;%^W`wklT9l]vV[;`: 9F"nlh6D"(Jx>vaa6S.V%F&^^}n#}i +v^'MeF,b0C #D@0d6ff6[1Df@ Mg߶-ƣDaψNR,h43TM]bUUEu wLGa 2o~ɗ~zǻ[_Fn֯:??uݿMB2C'Mx\gݻ',Nx㡃Tmł}L&):,".& jxxw].x,ꀹ Oc(FSJ"*zyAGQy^:-Pkۥh4F%&!S#Q#P~}y["ժ(jVݮx`k^{5<܈kZrn_񠯪LE1^WmHp8~/6V`TniĨ1z1HAဦiB"ͧ tE=VUrdZfM&|"gѾlPaZd$cnnc0`B.ܫ屑٬E`$ enD$*6d!qSMi(|>BP:R\.c] NSlDBbZ-~Q~em/+^t`pX6M(lRYDd2(]_Xʿn72.F!r!2C4}>lK+. b dG08&TNss7?/..Ln{.|>>C|>| hZb aC16=WYZ-z=k<% ϥG=gr1ݻW_}v]8'lbB橩X׈Fz4X,"lnElBH0rDѐTE"r4ISBepLI&jFfx^#L pvhq(@3_}OUo}ozܭ[Fn֯t:r|x:~4LPv?͠(ěA"TJHӜf2u麎f)s";&@<A9XZ- C'CUUUNɘ=&QnpXtTJw\p Lҷ;xu]H>ǃgϞx0N%6 ٬wAلj|>1V_$v -!Rfk&z6hHHy\"T*!TL&~/$sNz' TLoL%_۾^=r:3Zk=t:pD@ *O>}I9 l6eiD`RMvk(Qf^/\.@N!OYWNV[,E, INmf¡:{:T* R0pZ93-"?wޕHBbb<Kѯ[-%1fax1RՄv7F~H$q=TWkUX,[ÇPUZ .K&<m 5I2\DUUf| ݐ[w|88::tXX1Jp8$]!Jby>K<#l""L 4 B׍i=y^| ?h'E6r"JaX s6 3MʡT0HC X,rZ-,V꺎D2)~z]fH!Ƶ MZ.JՒF0Oil7^L{\рGXN,, z}^ۓxߺ؁wk~E׮Ѱ[k={V,KECfdjvE\6J,IfCC&A"XPc񥓰F3rrh4hZf/E"R^L+)bhjϞ=$c>f@m6^x!jP($h# 7NhdP,WrK=꯿pZVaP@l6id_Ĥ*H$\,.AflVj888@2B_1i3Þxh$[ܰ,%nCuHEΛ{{{F`,!,l8X?SAb/H$"j *=\]] fÏQ|ؚEiR,I2G lX.8;;ÇfŦ¿פ0椝EL^L<ƌSt:zp\GRb3P,ٌِ#x4˝rd(枷ە6IbɷE\Փ)@ BALkK'gQ {6fV+*6Mz=QVix,˵8==EA5[+BƌM- xWQT9 x,JP((/^HaooOzP z6=٣~'FPvx<$IK(fb("m9q#a/E<jЌzP8D#9` `mx</ ڣ/ޛR PHV3T* T|N9dCqmAxJ;m?XVLN'V˕`aCK;_\\ fa^jj!~g?u|ޯ5u Gc)뺰8t\22J 0 &D"A=|-!fsV X,}Ql[z=tmqf2a ܿwOv~_> ͎l˅^{ TJ,?NCM̦Y(Z n^v t`_{}r `:b-f\N>FPzWWn6X-xO80'c4uFqT hrY/i/7l&2Q׍BvlCl6[,+lvL3ۼ>s~0̼^Ovb `:na^a<=O?E4x<m~&dHMDiZgؾ̯ōHf#N*.G0$}3asi Bbr8Oe{Mz.M"*x_,W3YlvIQfx$Ӊh$zf-XZ,u]d3'X 8]NRX,Xa{)-/_5m>Gy]nֿ5vk~Wјu:w޽DgvVvyyL&j ! =w˅\.'mJ7lx8S&ϩp8D6 pLMA"Dz̔a<!bl' #.`ۥ<NSoܓLNigP@9m8C1H)1"v;Ͽ5|ލF>zߏSb1 cV,Ȑ~vzv 2t80BuRVbvQ(oϰc6RbR^R&j^)ÁN&0A%P(ڂ (T@,|)yl6r6MeQ2ŊEݻw0t&łtw'V^ÇiàpIHQŢJtX, YTP(fh+0P|-M5 dYM{.]ס:ϢT*&frHd^n6rf5LTs T*\^^`Sx l6Z-iоDK ߰Af+m**Pv1pttlC,K(x<-4 tZqy}Z-ܽ{HwW<;n֯5vk~x<<}zW^9wn.͆L&#S f'IF#<{LN2,qz:Ik4RE"UAF2iN4 0%<$26s͆p8NUUeF1xoJPaP@C&Aç~ EQtb!EM _ݖx>Z-.ORX.({ɢl&0Ѐ|7Lh4"f)o6X8@В(BXoVrzP̐Q/62>SB!8U'|MiFaf"L/}Kf)׋L&#x<r z4Ml69:yhe6^ t%DgϐJt:nVD"o7o?W\,_1$9)jqxxT*O PZ{l8??G2D @5SqhZq}J%I^T*A7Օٻbe*n Dlp`"HH1- äP(=tXdX,ZX,H$1EQnzEQc$WX.p.VV+43 vB`q\xaY~)}R8 [khح Z~ah}>Xo e,J#xͬjE[7x\& pXpțiH'p P%)t9$_&MC#Lk!{Sr, e KnH$j26VV7IxZ,+N? m ~_ &J)DVíV+ժ9f,8a&=" 䖷?x|0z B&$И~TU;GV(qx<X,Q(~Jv$ん^+f Zz#Oi]. ؤKZCL|cTU}@bٔxSEQ&r.ŧnP tvZ԰bp^d2D"CZE./h42&fCSҿX,D͢|>b1t:1 =IBr**%L|R$...M~/Q4)vHӰp8ԠCU4MݪTg2 B> Ƹ/XE}0d8 ?jJ*S~9QNOdl[MMD*B^h4d2[{{{\Ei@.C,bBRBj'hGbSn#JSMA*RVR&&|X,"N {>??{b@D2t:E4`0Qv[F-ެo4?V+TU`'i,P]&m?gmڭݺݵk4n`pnw(vrX,n/ ,K4M#H14}(yh~IF1Ly||cxB}35S"tg nCAX`X;fl6L _tdRJ%J2ˉ/#TU(Xרjhp"aG@'V^G4wǃVo`"Re>'O:r,duFx B IDATp4 =b1b1F|O(S&b^Zr,f#JR!a~: kF} (o,KL"!L^286E>'|r ͆Ft %GǙE; =*PSIp\b"SJ$|bd" =)fVVzvez=;-þC{KCN*P7T*@&y|izs8(JRdZVQP*ɓ'9t:Eͤ^C LN-|>TMSQl6D"vI47V_[{bf઼@ݖQA>S 21?! R`:ʽhi aZKl0aynK295dQrPV#AsxBA\{{{|EVh4l6I8b  :ݻ#͢Ky[R|>4 I{n{|RMˉ2 !XЫt:txĊ,~$ 4 l6<a<]`;&\c)"L1z1,@BlX,&ITQpL&l6iJZ~~_&BZ ;\ L!NjZJC1J|X0˥L׽^/`Q"X-ar`0($P7@.K Lx1vznGD"F#Դm廬9~߼l{C+j8 xWch&QLQUv]{lPfT0 Ĉ^$b8"Hk>"$n7O/ n/n [dz'޹sv{'j&kg= djp(rbV@PH&GGGdpT*%11 w 9qh4߇CZc2D1vd2Sq?N%~LדI) H$ۍJ">VrZ&r͆f -N ^/˥"4Ra Zr>x^)ӟJ&1@ÛQi x>B=cvITr^'țM Xf V_&o^W,ZJl+=, dbiPUjU`, t:H899}*b?h$~V|NGnWAVČFF̫Áj*N&'dzv)χd"1?AQ})(7('s2] T*4MV tthbuRe:"HH4|v\l6n_=xP*DZ0 Qx爘vRn0*|>Zҩ4k' >`b&i7Brfh4M3.\]wp.Ñ|xӃP($x,l&0&r:84VAUU2v)$ "K#flFC (*6ʹ !.Hw_[8='|w6h">88@C0V^}Uun1D"MzVX rY {{{@!fIc^D"`0^u黷-CnWG& !ml3nC4(I?*VU$z]~(LX>&ح^#c6 vl6+A4vūK) (?>u:ɑm 6Iy4H(mgꋢ(xHhZac|d28<<B0y\b4b<[,Aj^/|>>ct]ГI=LaAvt:Qդ@G EQF elx^l~ZbX:-VogX,Z,oZ^xĺE[XL1l  t]QՍUUm2Tڡg> 6b(ip3R)VY/Z"YxDkeHS ZF\+NVHz(8::rH"`XDb0HS\.l XrL"1Tqz= aR)iR) M ՊgO5wڭеk4nKT/..ܹh26&2,I'n4ɔ@QQp΃{d2%$sp8drJ|\.,K~yf0D$n/ ei愒REQ0n2TPRP*d)eVzHDYg(v>O$H$"0q*dJS{2F) xpzVs<_Gl& $0vgϿ=I!&R r!ARI@iGOzXb{' X V>cb1QЂxx (b)Ěͩ!v[l2J`wZȈX,XB^aOFriLi S%' VK&>1E5D?G޳ؚX4fj"2^c<I4fj&t:EF24[\,_|/ k`ڂ`EQΆl6m^~Lƿ9 c 6`=l :D*fS@ٳgX.Xbv\c>ZhX.rv8bMaӀz.ZhqĞM cϟ?dh$ S%Kʤv/`٤L&1 PV>Tl!&y͔ N޻xnl[ivqD"A<  1=^bvV_A(^NG&yl\BR/T*??E^Ņ"8b<yfm6t:e&IxB$FD1)@lD888@(ɉVFsT2zNb8l6z# PH$G}ճ~t"A~ &{cooO@T0vkEk{=7~iH͆'Ŧ(2Adcz=aFr#X<}TX,vE20Pl6Ro6<$Ş{2EQ06-/7{i d>4}ߩT ϟ?kKMG"x5 dCG<ÇA6ɤP ?D"'dC^հlZtN&ӯ_akkX#^n2 TU`0=$S:N{҇iu]4E"l6J%l8j=@\.D"if3*[ZJ.Rnfal6eȑJTD~j~|RL&QVL&a1] fMANBaz}2`>sIdy>|>qx<Z 4Hj54 vp\N采3TtPBqRl6aXny3ϠVqx<]9'L3-^ǰ.‹lbFAX 8`rUnF3/HRˤf B-(0\.0l6#I,'\.h&@@6. x?mT;0@P( ckk vC|r}N~$T\t]i~.zzA vi"$ HXL0D0A"bLP 2cJQ<!@]r ,iI3n+Kr]ZC=?h4 ρz*X8 |j8DDf ǝֆVǃǏ'ڵkd2u]Lq=-lD"!^n˅n+t:6ĹEfCNt:Fcy:»^skYtؼ~ XVqXV22b UU1Ne` *łd2h4j*@@BRm:QDl[UiX#uD&\. [>O,F;Aoz 2]׺a]Νd;gaҟ|;~s-JIKܽ{0[=PrG-EWj:::`ssF ߇`Y0d6yTuE2<` eL8>>|>.J^*܆b1cg3ƆDJRK/IC1L&c|>hKnMD:^YZ'<f" a۰d;;+:Yle{dh6a0h4 :#91 CFy5#:8\.ُ~/X=::7MSƏgt݈F8==\.hm f<ú0.XLJ)͊T*a<l*~7XLH4#A +zPH|^,>up12,-RķKp:8;;{j8nt~84CnNSH?ƆN/Xq$FXFrCY3b<54eᡨ'"~~Kl/l0 8NDQJ%lll6m1zN7s{Ά%*j>j M&\~޾ّ&S6Tj5~T*Kb A|6Bp͆M:p\X\WEk 䤰jhw;/ؽr nݺb( (P@[[[0MBA/ %L&# km1._N;ņT2~N#Qɽ^P^O F{7u(F0 PRIvVUb3 R CؔZ*Iwxy!wYzk]ZֵvfXzs3m>˨io* JxZMVkZX,Nx=0V 26j {nB ?|jJc,6$|>4t;;;( b8P(`XHl6ɉ4`S!Cn(6 C`HXVJ%I9].<+>Lr`K^O6h(˲a,VU1[w|.{&wp#6[ozѝ6wHZ$z:4M@O6U#E; NEU˲ӃxL&#W^ETcڊ{9Q Cɉ V 6VUl dX&^z^Lz0NE=EMkQ>_e,@] ȱD%MHcelj"}DFJ*sJ/n|c j YM IDATZ8ЈD"(H^t]*`e !vu:DQ4ͣtKN|H:C"z(x]ꃓ1Tq{:ݺu жVIG2^C  Ca1EcKlifJP*d {0d0h4ĊxP.%ژsTB!h&A'EQ`E9Z 72r?Zl"qejf9B,u:F#!w]@xo7wOOs~MW_͆BoY,zR|Zgփucfsáh4|w= '~j?|3y<;lfXx>vn[ : .`JCԕ2p@4 ɓ't:`hvҷD9 !>[SMu("E]iYV6Il@ٔj5+f)cvL XL"4 M%洞7n"KdaVC p:"Sg,7ޡPHd~|}IFXP>.o߾UxTUTU12fMQá_Z^/,n7rͦp؄j;w'? Zl6.^/{'R  b&҅C`0`0~gd:{2ϛfݼyY8mT* t8V! T 9XL`\p E BkW(`0^Ż2u2VjBq1_,\Tcf3Vq1NXMӞT*Qk amDQnx^Tt[[[zpݨիdpLD"a CɝNFj8d*)v ̲jPoeaĶB+s|^~N{@ߏ޽]_wLӬ|?_ӺYa]뺄RUۚJNƓSQ&{ozfu HgC4ZՊX,`#n鴤uR ….8€<~ol634 ϣlPt:$F+j0 cb`0`kk \3ZuQ5n^Mlnnxɓ'@(Fn t0 cMfDM,#.Yޟͦor5L`"\K{b[9DDDa @TMM&RH2ːr500e isÁ dd2:bV@rH8L0̈́/BPx56Mb^)]vc4 h6:OiL>\.?i .9- Ff, Ñd"@ Rɵ{g1 Sod {^ݾXqk]ZֵK(0FZbw& .vD~3}d2/|1{zz*g)rVFUUԪ56lVz+Wv?/`6TaTux^;V-UvuIx'5|<#L3e@~_"0l6n޼#ܾ}\`>T┶' lnnQfN:`X,v ̱jAtlnla:B4Mh >V~#ӄ'fV*lAk6BO&"gӣdpf__㲏۷o߱Y'2& soE9D^xBlpc4Mǃ6g4R(R)4M, e dYWV&rFw6{F!~/ ` } H}ՂarhZbuhZ1U"N.$fөVMD$t%eO&8?;b4aY, ^o\kG''T CVX,u]7ny j2bc&A2²Hr߻j/#79HdwPX,\,b)Y`K+v NJcm5 -Jzr x]X`$1L&ct;mT'ѲȜFu{MgY#TjUsXmV34 ,̦3iONNj ԹⶌW1.U9®(ػzn[L|(pݫc@0L`ZxXEevl`9,"L`i3v==h5[2P0M.A k_PN.~{պփu뒪P,FPvht{44O>˨>z{|g9Ek2si'h.wz=tVqI`k2P]8VnlggGkZq=}kB͛71NiIy=wb0}R#Gy2Lnv^-l6hX]\$S3ᢕj]R `|4 ^R] wJc s"! (P. GGG2,v\rNZIgٔc `pToTgLˤ].(x|c$ XVaZFg }T 2V9瀃0bmfB(r >/>_óPV ߗUݖϐ>ZHŧ( j4XnjL&#\H$+W;7ڳ lz]$n71^Yd8I(C.1w}&4}A[RF'Z ha0X.6]"U_Y8  B'BUU\rL`T$ |>J9^D8B<766P Qa$"A ^RIއ:$xwa 6M,H|h>1=z{͖=͝_yzH$t:CV^x\q", B\.evJCѐom6ʕ+PUXD:6J@ۄB2aE{u z=*va^*&&ID{@֖hi4bx C 80`\.C[nSd2 łlg}V5Md2A:FVN$__zUl6{ttO<7,d.U5uGa]Ǫxe@xbD4yMQw7>՚'zdvLt:-.&;NEr7`X0NW!Dn`)Y>;;1pX<{m_VěqOZV4Mz@ȸH$X,Z&ѐ\zF10. 4Qj7NNGGGGQ$LbvaX`Xa\5Zm]u@x/&h1e~_ndR+P)C$cѵbZ}^\.wT/b4tjI 7$TUR|>BHRj8D >AHeܪ|>mex^lllZ `9b888@PhH˅X,&9H6UUUsKah& hZX phЂTAPcE|>!c.CH_l6j*C8䠎5T*QQtPv:MXa6X*\kxp\{G׫<J Pc|vz]#6nW#m,Ls^*v{z} sG94HuiT*HRϖ8h6z%L4jrPH@ac5)-JL&3`wwg.H\ bBA'''򗿌4MPU XEFnP(p@|6dYR}/УT*hRHdt: x#qWγ{U6N_+ÇF«p( N9nFCP($C)*]~?^x@NS|GRa_TAs8ҬsG|JAd2)+Un+ .(:677Q*Zŕ6Mh M^:aE!M1En9 =U]\.iJ%Q$x^ciG"%)Te4MR)}^\.cBkՒ;V,)a x<.C`0(o[VFbQY,p8ڒ|.<.wݳǏ?z2+]XU7b6vr9WѥQFv*Ţ+$X`0SтE~Lt6{eRb!J Du<>Iĉ2~k4>NNNpqEA%Lbr1 tZ[ӉfL&#ӹBI$r9lll>C,|>{}P(.8W|p~(O=;?߮,׵փuD2l6J$Q%5^Qc0V^on-zO2z7~]V?J`J[nC8pT x\ (X,pܒu]|>t]II>RUU!t@1NgȴHܥjiK$ݻM4Ma:߲1K$CA$Q1QBvl6~8dz&\/v;Bl)8 CFtZ^UX^z2::yr}nzsRuy/h^\{2aI:aHZ.N$x IDATw|>GvkKbiUUMdK$4h6P@VE^.#H$ņ!B4׾u}k=hX׺>uܽ;Fd"qvJbooOBSn(ݝNgM}Q=A򛦹;nO<j<ƫ:\ul64 "p<%٥w[)EQ& "B:nݺlN 0 p:( H$PUUb-)7^~zG&9(ݮliL&_u|_ǏZ!yf3Vd^vM/6{yoJ76ROe$VlVմorPEx0史II=)T(b\.h4 "RݖmoFܸqBvl"NcssvHRY(}&J6 ~~_#l6i\Ȥɗ^zIl6[[[ׯVɠXR}P*ϋ(-ĈD"2ȨT*|I6͊Nym26* ;kݮQ,=/2le4"p~ !8ܤSVԠvVU;<>9yrR~xze8fS~p0 PՖ1+Qf3looVlߔF H^p8$N0Lׯ_źXXXf7Ln%inc6ڵkUU榨 hwk:٤CJ%( 5+LJ (u]_;jtG}YlV<5M|';KO]Yk]PAú UXz{ofч,PTGҳ%|>;;G_ү}}nXd=jD"8;;i-X, À`P߆a@ Qդ1Yg(D}@,Ͻ(p(X,b8>>F 8`09sinpm1X-`d-' @G80AF!+Z/{HT(-1ϑNAQհ/ *ѹP Q8}#w7L+ ݞLl!Clί\"h@-m(BP+W0Lp=ԟl5$9qF&ɉ=c4MAQX4|FC%>|Rb!(vV~D4MbB$@ݖjx e(G `RIT9P}{$(u2 ˉ RHl&|3d#$Iܾ}|2rDA8??f^(V9J )'NS{ $l\.9I Døh$&Fhx<Bxge hB6w{~_&C;{o{>NZקփu;F#MbXF`>lsCp8^?,<@ajbw&ٶزӻmaB*F0MdߔQj5 Cz=VOဦirs妔2^nzi9|VAUUZ-lnnhwwF!9$Z-y߼yPχ3AP~+)YZ=v+2N(qdA ilΏ_c&fV˷^=l6xssSii63 : ϘH.^'@O5%,C$*D"a2_?wFZM"ncGd2AXD<ǭ[N:`Peɓ'OJkA|VPxRIWX ln}`#i!Yl6[P47n\r"Jx?pzwLsf0F'eTZoh4֬V Fl(yj˭l Rnx<.2B_bX,VX6L&cٸv HH*(L{'< T*W (pNGܰy^bvWLc2VYFR:OH2@p8 H$y YTE^No5vF㱿R(0˟nvJ%س(Wt,Sub6QTL&t^O [[[d2Oq0TUBTUիWe`sюC`0P0 $IQ n8Χh4).B)ɿȞF!|BG+T frT*4}FbSa'z=Z-x^|"2t}sKρq*/V+nܸ3L&QyB{9gBS7QEllF2((JhZ!ȓi94QE i`l6th4d,Jfj;7ou|ѥoP h2Ȁb Cnoo#`bQlv]|.M,DQXV<~'''caۑb4`.#BDźX,`H0ėx"א4̦STJ( \":aXp5ID woc4IJv6>|Tsy|' :?ϟiZO"zy]ZAú וOOsor1L%I+bNSX,өɓ'O=2 6гLiL<+&,& @E"@"7 v]޻hZYoJm62.N{}ɇybaZ&si$14MC*-)c}>9t]Ngggbf1 C1 ^#NˀCub1j*2jp~~ݎH$UU%4MdY)2nez=DQx^-rĨb*jX,^bt: ?GϨ&h2i6Ku9NQ8Nx^a+,zP*0XEr𶽽LXmn):@N2% ?W+* J Bp8b*u[[[  [Xz3tpڵ%#fhqX,);;{ۿԭaF* F"LcX, LqXDZ.?('wLZB CѐrʘN )%,DaƆ mX[ [Pt:XvZK.bC9Ae `P4"h3l"{ޟ8oǏ=|6Zקփu\.\VlZVMJT* 3;{r9~o2M4N6%Fzl8=HL%E`C j-D*npWv58A8ndzL5pS͆t:-j*`0 (>j@ S$+t]d2@nW^6ID> kʶ>j/|>;Z,FV,׵Ka]}0Ćszz*jbv˝ui'D".;x<k׏^xAoxbB Avvv`6 ",8͢䣏PTFaDd&^6LҒr`XT*L&UUCVCBEQP,AV`0MD"0'!+ %޽{w,o7LtfʁC7߿ۺփuWryCUU;vC`cd|F+ `0@*˥froߺ;viXL^tZ<܆u:h\X2EZ.L&@zj\NS};s{G|>L&vHӲi# cssS6ylx)gdҝNlof<i 6v "L4MZ-uJ%8NuD"b1E7>WGnwIWGGG|jj,V$P`6 FOScggGL X,P,El>LD ar%PtXw6^Wd # 0 R)h#2-Z"H \.QeZ-a EL(D"Ney7NCX . &֤JД&DL8Np8D!E> ^z%ܻwOrdKRA$鄮h4f(4 ~NGVUf]pK?pX߸WK%S\dݹױXjB!xf_rto:r]j=hX׺~E/|iR)[,cer[mN$np-'>ɓ\RF6n7 ro zV (+$-* <Ţ46.K" ?~,7T0M(p\Z-7mVh_3!\jl64>O8t:p&A$֙Xw%"22r*SIՒ[4vL*MtEFdMDG$Ym&L2$gH*k̈ }qp^ehzflXQա2c ﷰZf9mLngg`f>H=:woox62)zc[7½CUUT*DQވ s`24EnjF#(^#\~fYcN D"V&@1}=bia;17ΰw\|#鞩F1*aB2P(t EQJ&1Na64zNNNx8 QB`۶Jv}G\:jD8X 2`&BF JIR<44 e1a̽EI1$af%plq4x! ^XA߰mϟ>-냃<eB(Jے$i4vPUy'{(0 Bo> Ox\$oljS 6OPc 80d1d KmF2d2ÇwYUI\|RVzL6=AZ3Z-K< EQpx^á'P2( : N/$=OCwRuGX1Bʜ RG||>sDQ缮i dX.Z( (A_vߝϗ˕{y?ʺs玖g$~rJ>\''' c9 A % ēw~Ԧ> 6OPhTd2H$L& 2587"B$, 8,SRݺ[,3?7IF1^g+ ]"%a,>ݤ1A B{ϧ)>;"NueBUUA43(>h׮]bMMmkh3H;ŋ%L&vX.>Ϟpr^`}>(MDFh  0 T*oԒ_;&#IQnY\.1YNj߶m#az|  ^nt|_0FٗN)s<c8rJ!VjoK'+(QUUבW\ot]g O>tx3Ox UUH$PTXC*gq#,M[D mt]#pL놴j}l6\KBinu-Z'b:͛<]cRi80LQ(غCI=էf^@sԟ"Zt:F|>f@,2zNึmKR/A z1a+֟k}뭷Hs7!V ł{J#IؚEѴ4xb8;;ceqbY&rSk3hԦ>aK/<כ&)0h4820 <# yRKM$/a{;Vi4eM~i(mfv{ Hyєe#o|>aB85"X}5Fh64 z%д|g}A&; 5WOdz EnB܁qCK ,{rp6zEQ % ݻ>Rr:nIc&[~:2 u`z@$rHχwye؁@ncGsQ"0hHBh4b@ F׮Qzq|| 4va&0"'Zukk a JAp`X-W U0D߇eY& i"sD4Sgt0۾\.y?z뜒5<G*$o\dܽ8;;&ҥKxlݘfgYnf3%?B! lt~{.q"ݷ^0?db8nJ|,HeF/ ~?rb5F4`5b.Ai=;x˲$}HXB$!k Ej~VzF< ji`@c"vk׮5TTXQJg5` SȎayH0aY֬vv ex<o6A@$aDqPFxG/ώ+_D? WeY3lvxdžA,0M(8ōWR$oYnjScm '4Ֆߧvf3nPEWQhBSx^T*f2DSvmEtIWT{Pe8݊D" &;lH.^h +x<&ؒ$gڀǔ O>ix96(fp~'fkh t b~bb`B&AVc;ɹ-BPP,c(~|5M us8r`0~Aeq~X׳ X @FNMXeO)"`\rPy#KjjrGEQDQ Cl]$ixUqs&27m4|Ͷmܺu V (YRf3L&p\ؽȃl'²,D"Q@&N5zT*H$lWvp2 ._ ۍ^-eVl2xԐDn9ML!?1by[:0[zZN;V8C4fӤi$|>Ʋg]p,j!N*J3\.#Ͳ. Ulmm4M(dM7+ dYm;ųI aač0M}ŋ/ׯ|@. . '''b~o>z#sN"~z Qi=笖"=ÔEQaX,b:l  o&rSk3hԦ> iJ2L$jl%ȓo3`gg(k<#@4`>smaTUW#T*V*ܿot}`vw/Lǒiܘb$"n߾>rl6Lx<Ųgm( SingHAp8x8Q5R6_1 ;5z<޵pgggrݲoܻswwf3/Eql:'mPd2b} f3a@b끆bh4_WH$RbY8>>cmd~s:2aA S(FYۏap w}qTUj5* 7 .f h~ى]lPx=Jbx3k* :sI܃ )]e,>mDQdy>gf3G1NƷv SP(Ff:FTUh4BP,zr|E"p8,)V QsZ1U ,mNwXuod}6%P$+$!I,l8Ϲ1%( ,p~a@]s?ȯvvl6D"pb@< FG?>DBRqjᥗ^1k;d2RT*Ѧaɓ'/~^RRC%ūpWH^uRb<7ęLS8&[h67dM!U%%8 <Jf.XoYL0 bkk c{{Ta{{~^ACIJQGd!+)WUfxPW M _Gz#kR<>ًhFLj,3AmJp\v& ?C rvqxxԦ>4ljSЪV]zu04zYzf9mdZ-NP , m"%I*o\]E`?zſ]^/0@j"#\.A~@ET'''`zߏt:\.Vh4L&4Q(x:Fi&8^p^A  oi3h4 <t1rhZ:dYXWyׯ])K8$HR'Ea mx< \%c"@Z(|n(s:HeLR4'}x<NPUF T*&->4\$aRTB=%Z<G<GRap w0 xF ieH??ƣGAZƍ#IA14Mc IV,H&}/EbLv9T s_uk_zw7Q^l~P,l@eyz<2'H_&R`Zagg^L$5 fO>EQ>4 5.ٌh#In޼ÄSu ~( PRDQ 9)rönݺj^xQ _y(?@ bI5,ȐGJޠjdYp8 ˲ IoI&O{ڠv]ؓ@ r@ ^l(It]))NsI"`5³zv$Z-f3޸ȪVW^yDR)1 ի(XV$ W^ƙ6͔A}bz묶4 ?f#i_DA)mi8^]yé*,b9l6F^ EQpm!3J999^VY*,\.M[Z-oyޣ׮|JdlY`iD34z۶GT(^3SZ犆tE"D"$ \QIxJL&h4xPARD"Ƶkט;0d2 @(LV[q꣬ݯka-"np4l(i yZV1qrr,{-(z|V&rSfаM}/~_%492ҥKF.%+?I 9W mw\.1Hjr+kL&GZ,2|>ϐ?00 X,x`6K`6QVO>tܝ'Oد0yJXfO~p8L&rD"۶yFjMӐJPHlph4 (̇ mOyᵻq"bk y8oFj ]G׃ey=1O_VRl4! !r#)"6C,ŋd*['8R bK/&]uF8$4 `{' I<0Y A%NOOX,pdY׿v"vxj)7d7JI!(gXaIuX%81]~?ȆdCiGqxN=,ۄC^۷F\.dBM%JŠpʂS(v͍(k0UH|4+O0@e5a'oݺ,eT.<*>|,4#pPX.hp|Q*qBhMjF1ĵlC~:|>z@ fu D9˲ضBL&LShB{=VP0 D"C?OZ=xvwKeY& 4 LSB!qXT*!H։/2D6Hp!TUE0͛7h4%($)w?66Mm^׮]*\$)&XQ}(4^<(ixX\(QzǛo ׋ub1b1iwJ\Vk?(0^,gYCYzɡ 67sƛ6Sf(ˈbT*hN_-Kf0 .87_Zptt۶,xPPVa6B0NyDoMx; qE<~zJ=o ͝;w`Pj%`ʕ+eakk ?F4˗i[#b6x(ɁV :t&"w{aoon6XIO IDAT86=R)t:Aq\zQ"3 q(NOOAE 0L&pxuiPóM(  qM1Y 0YD _[Ԍ*׋FА@AoG8%YVYO*-$INX1b&TU-rɃ_ ss54jʛ}EQH$9߷~!t|KL ?̝fIvH$jj*2zƣ!NNNzEHHJQUB jL&+рL[[[p\޽ 4ljSzr>Z.fYn6|>ID[`0ojhJ pxxo4ŋmw]Voh4&n,[_e6ei&Kyϼ`.KatRA܌l6F2pAFqmt]iެM&K`:Y /r}㠠g2o-˯Z ~ddN?(t !چ\.&Ԙ\.>D'ٳt/ZS# ɓ'H$$eƝNrF-43žPxqvv=ef 1AzL>iR8;;f$B0cr{A4Zc;+sN48==XJNlAD|nN:$ bUdI#xrr z3 @.c5ɤ"ZQjCr i|#>b<}rrgK%=HJ+@d2l6\.ǿO^Nj/v ..Jt:<#% ("a-˂:Lgl6xY׋X,-m C>P0* u*C}Msf;!(zL?,/DQ,+ q4 @O?a0MN^8Zx6#\|yAz7q'6MmSP@zvKB1ozzzɯ%-_$(.d0 Vt]v]~uh?~#Ic{;VԿ@Po4?z[/ыl6cH²,'Fr]V0MY YyFnk׮akk '''D߇he#<5ri"J ux cZ!ϯ9}χf}L7n_}.}_ Ԛ4D:ڢ.M[`0,gw:D"N@r'?sJ~Byr1޽{ytt`C`IQ@ZR+ϳ>_~#(Oe7{prdɠڅ ^E|b^  JX.P\WhI A0ƍz썧{b4akkN|&V+ngأi[dYgN+;, }$j0 :*"G,'*Ůc !!gY>| 8 QJX,cL[TUE$T5(j{y,}i4r@w>g3l$Ie,p8rnlRWi1'2/,w邇nBE6e'YHBj"afvaqHH$ WYL`0(-u|k?Ȧ?nݺ5R#(H$I+|%EsbdArMmjScm ԧ{AwlfT >_oeYF,F,CR gp[wsc19|3_/z7<.۝hZ緖˕FY4YVNhh$q?O[8Qv !IB!(t:@T*VM G^Fp8MiH T5čt:]\{~@ G/R>s(uI N /a2 P>~xC4lfö{LYBoRvA_.)1sFRsؓh |9`2ujV%"s<{=߿.F.^*NPivvvXO >Q*xkK$ʕ+XV8;;˲prrUU9R2BCQj$hzL&QיA-zW\FT/a4[QJL&Q5(H $v0&c+"tX~끄{ͥ yOښ4*EQdi6[~TU(J~o' ._|4a IR$jgr~uW,@ EQh(WT`YvwwikXӨ3|W|4$׻:Ébp8H$ṊiNzXZ۠*9?[:<>\.W_?8x\>cQW\~ij X-h\zvo\7bV=_9YV]x礫Hh6rx㗿M6 ͠aSl5MJ% @u|V8`0>7 D(2ٙ.IZ%oDј4_ǣw3[gsTU[nW"h4:Jqf`74?K2`yFab0|>v֗S'"7Y<,MXW^yeDoR$a&NCF^x w>/B X}͛d2H&xsԭeY8;;jŃ{l{{{0 f>˅@ ۶q5<}'ɄjEQxx-2C1]:p{zvv`inA h𰔚QR\2BUUx?F8烼Z< eHrp&&u:>A I- 2ܹiĀ!0m܄=Lsn`%rƍhZ``Lνa˗/󰇞4j2r%IbEV+NM999 Ê@ +W\ :ZZ̓H$N1PDxsv9rR J!"n;ngpҥҵkryQL`d2Jz Wt2e7{sm$q\n$~pQljS 6OII>ՃAU2ʲ 4quci4Mn`NvH$MS,.Gx' 5 z=VQ86~sAt:{[[ߛNtz [VP'pf_>Bnv0$7/#d2|!mR^/oM˲ǡ:~_+!x3'6P(G$c?== F7N` 3c].D"ps8:u3L̐Wb:"NseY t"=L&l۠4br A˲'Hr䁔eY p4& {=4Mt{=us't:, ?OX.dBeѰ-" |_R1Ai# i"@8$q7ُp8t]j( . >\"]0k?~h|mivƵ? q a8"Ls"9_DD8Ap`VEUá/cjri@|>p8r QcIH[jQvf%^7ol6/ɲ"B!gU.'vGro/IGÚxT J% ]o5MCek5P؝F' I3ldt: sȽmۆ(Q^a`\2s`탗񉞻x>ޣ)]nRam)A_OHt}\ X,b8"psa&o-˂m۰mC.&*H`Z1$<. Lh>nvw.E}u_'8yUUŅ 0 XVN۶Q;v^^g[M,;# bZq3:LX,|uނSXn7SOLL&PU(2U[.>GGGF4ElpVejBBD"uZn/ C.iZ1dP( "rʂpCQ~lmm^RBM0 0v%F#V+]`hgϽAgYBX,SA6RB.\@۶@*"ZdpxxQd` BR$ xCV bbеfqrrZ AsG}4y*:6D"FX,t/GpnfNGz…iZXX,`SiB!r IxB mޮ ߏ/^rA$, \ry(`%Ja64M~1R9}Z~&>_[hcIáQTrA<4\.:JCbn&.^ܻ[l,7Oxm ԧ,k ˾Zx,Ijam.\4~ v@ t^1=/v ^~klaXBIx:øHNm6fO_wh:GSv>q<shKZepimzzE_ 2G1}nvU5Ia: 49k赃%+7T4iV̿8<~?3P~`qvn߾^VŠx"j @I' Vq!>42 du0X7$Xt]8NKP|>t:E<14$P,!Cy%I'b4"6Z"pHlI9B @qN"bmۈĪ(]sa7G>OA]>^UU@(>/!(j!N3EUU|[6Q IDAT [b^~e1?!ǿWlTϲ"dYxg<RU~Lnql4Y&31<;au]/pS6MmSXDxRӹ,i[uȲv>|>|=g6o n24V,iand4$IB\l6C"`)2q vvvxJCsx'[B>j4K.qR)>ܟT$,#a08CYNYOi4P9JM;@G,㧭|_+ʼn[t:Vtj5XPAZ9 w]nE2DdO$,x饗pUAt:ggM94wc_@l 3TXAJRՒ*MQJXN#J}jSN:IK"\lz,kk")E&d. x6Li$3p8߯*$?ӋPUU kXSs;QstEaE`04Mlllg5Ni7+2rYzj9hXֲъFc|>WiNxX*Lo?r9ho$|nc8AtKti~%?,WFQGՄkᏧ̲,6swVjmm =ph܍?Ђ\Ўi>`0F6(LjDTVڄB!dh`4rR9pFI8񀆝~}ց@|. ~Dm^/sAx$suB d2>dYt 'J~!AF8F|a_ B?3u[H$NxL&VvpoTՁJcO]]͝˕~7W뺺 Qqzz ׋L&vR~~_a&0wR`$I /h4VǃRVйMC R,)N=[/g~ N⋷:?wѨyk'{]WVCRbx{{K_/޿0ɲuk9hXֲ4ٹ;JAzؤ,X,q$tVeq=5\i6Ңa6 ^D,#CQ|Ǽ%b.F|ƍjgg{1MAR$ P_q_Eb,e$IP&A^3ammy I|e h4X  9F˲ɤa6F}Ǜt:{dipeAEr9j5Z~x<)KgLlr,~hL<MVUD"@]$8?Ad2A>ǣGx3GJzJJd>~![(*<wưncgg8>>FՂ&oi4,w6d2\. J%V\cqlx<g}}8<TULS  bgg7:Π( $IRbx^zDy_Q*y>׿L&~fM7\nyIYb֠i_31z\аq!qge=#4,kYpp8h4,aR\ao4 X3zH$h4BUŦc6bzT@\HM&X)m[6 /@xtQTZ?<.[֭[z*s\0`M-Gv3d0VY6Mìo7ܩFU٫;;W Pߧc%9P Bh4hOqk"BBR|>pT3}?zΚ&LD2D:f;eYh6H=I~0w߿a t!h~? sۆaz*@΀nrDQʖeaYzkggxF"\+zMF#ykk ;)KEA2m]$t:e*5|D,lڐRZ@>DQ ޭVn}X,^idtv0Q.P].2 Ǒ: m^Ez=~u?ݔqea}}##sA&ܾCEQv)CXmC<g )rm zbDnߓ?b8b:BEmR)GrExA$Z-sV(?55d=!p8&3F$D(>T*h4 V0X@ ^}kmŢsjj53 IL4I' d -*|+|PUm3PGZJx<΃H$شOu-fM)bdY u4 Η7W~z,m#vcuu@Qި )^htaIaǵFQax5L&\v vVگy=>RUU4?N;_W݂X'΀_ 3Zֲ~j9hXֲ*7o]^&nl@(>*;\.T{j[x!biKO|Zl6azpfjAm Cކ.Izw4h6[oݺul<=@S:S x<^pJvFo {QN@??dw^Z;EQXprrFvj4vD fP85A<#cmmez:Ga6h@00[4t!<mg31HGJڶI 1p:b:r(VAGܺu >{^/2')LS˨V} NχD"Ãس.2+sf>BQ~z=V!Pv9$B4!I~?oD)޴ja}}s_"u]G6a8>>իW|pLSbK){JH7z Nca/vV?$EmAx^Nb-T/H!ŰHjh/@TW2*?߯u˽vsFh6|o"n ~Jrzۉc|HӸr !98t: AP*J(#F Pww7eY3xg(9%G,,ge-T0ݮ׻ݮ cC(~85F%vH,MĢQ~zX[[C8Ɵaparlr8v_4I /M7d+7w>jR'_}^jPOOK0@:,+5M@ns\ ܽ^/O%LM4%I^tHH$?cV))ׯ_G(BA"@Tbm9nܸ̑vDZPF|Ơ˗/~0?lf&yGUUSM ":$ݔ 9@9RQy3K\.hŶm-unNt:j ; vD#DQ :J FQ^"d%d2a"]CȎ Ij$Zu.]杚cMӐH$ tIAN-!0M\(Cufxpဤ  5 CdxBWXuNmlWj~t:C$A 2At:B6f Jo>7 VzкKko 9La}; j5@6p/w}?Z-6~20@. ׮]zj 31 ri$pmN`&P@ !H'ˋ hሷS˂]Xx<%ـhJPVr I:+8bhdY-X?ydٿ[*<}ra:xEl6ԙdD"nfx BH=\¿׊҅ѫ_l6(ŹOU͏>xge=4,kY h[jU%`&~? 4MC&iw\,P(6rAu:MxL&a6=z]y.R"6_l6qcDYO/|͖e! Auu]lmm! mN&k.mIu1?_ч3#jdϑ:&C(^gg7B!,7w>lk׮i,%IyT:0 ضN42.x>f5dF4{˚>YkWwj0TUEZE|{V]k˲TAòuAÇF:"ȝmt:x̲,h|/sef @0>8@-Anq57I(q`cN~pQt4 hĄI>iÇPUhe˒$!ciBa^ut5|fF"}pTX," j\p9L'"\iuJi6F#SRzj x6Jj5/| . AUU!tKxmHR~@]{=Q2u\_`0p8,{h4y@.z,S ZT8TIORp l s(o&ې ,,TsU5E#pp8d2r|>g%#n7[t]GRa1 hrzIiX|y?pu>e IDATm2 `DV"J`/qZ3ZAòuxkkՃ'ax^/RHE'|M#A@m^hP.BE Ce$L8ȑVEx2?f3DQR)PAVc9Qgٮu޾}K׽c+`5ꭷ (Jh=FQތr4rڢJG6Ȍ" HF>ض}H:Y<d$55SPj| n7 织B!N87n@\.s3zQ(K!"n߾D"$,6溮scA.bqTU$IxoeYF0&Q#Il"k_G! ^3T*1HR>$Ibi~"gϓ"4v Ag)nܸlMX@UUq=ܼyd@)edV4M$ d2n)ڒUJ~X,td|;-J$I(˘NH$hZh|Y ]QVl68V$R4MwrzZ?@|Ͻ TaCƝ|GvNs׾'O_./ⳬWnKeɓ'tvR5^ZAòu*nF $ d,w7oC#Yxд.o;>?*˻sN$%*1`YhcLh4d˲>kW.kœ{U*'7Oz=f6W,04 r?ռ t]4ܿDŽeJuDQ,mlmmOf2 ?DTEu G>+Ҧ~I&rh4Bd?hooo3n$~%?}(bB2 X:)">^xlnn޽{|p\x!+(RCy(˗a63,\.677q5^Y_@Šc@405*?׋3lmmq (V, b~^/_AGea>DwpTUx<LSd2(⤐4hZ $ vX DH$PV!2zdY3 H vL&!")j:9ruuy,L&h4I p8 XR6{n7)cr++.<#p( NOOg!InR øjprrcx<NP|/"mHV}bwNO~_UeYF:%Ir xm"=;2rYzk9hXֲ.XKmR,hӨ*g^˲Fht!vX /d$Φ P*8$ܐrbmYHu]g/'=ϧ2)g= ֻH2. ??;h4z>1IJ<|p8=~O048y)ȲGA!rb20=<lœrT*d28< @alj899eE0|>͛719" 7 ( (FB8M0M/\.+]Hzqpp Y`FgxCH7RsG(h4ì BH 8 X]]iC>?GAp`0d2hmR)`x^\z BL&J9/p-P^c(WWWuc82W$ ¶mJ%z8 ̪( '4 ?=$")>(uuuSc @<J ^!LB$8p<N0 Cy=xF~_/Gnllj!J!MVt:X,q~V[}{gW|OӴaHR ">sc?J{/,ge-|}vcl$I4[-~r0X< Ev%!( ,UUe5b{:. ``0W`0p8B(F$ `&LsĂeMx2і^! B$x6`2a/rִ]ٍ'^\'Od?e5 wp 9[5"a6rD"1)vxVᡳ5V˰lL&mR (\.4PEi'9-11VVVL&1ϑL&YHgp]g}2lt:3V:Be ]n9 l6`a<A>^/$.|>><lhMjEQ888@GCԲP>="H^$ s1D0cX0/lc@:f&ӱ3xb^U1O$4!m_F&WAlnl HrvFlN.9qppt@|n#rpE<GhXxT2p(0Xɮ'0 zGu:klt` V F-gPlU:14v^mW^-`4!8*5$/2Cu]s>k[ðF"jFDaeehZ >3X[zxE2rY˺4,kYj.H$fɲdr 6{AMa`ԣG 7K&& REmR6=00! RC8IIjY.1LYDmY\4 ;zۛJZwjQ,xoF `pgK[V|>dBw>-FNvQVFa&|>RTU|>gX^vL ˲PVz l&gS,"!nC.m,߷, (rZB 1.G ~A@@(B (읦Mۍj)"a`:jaj9; rL f eY8==E R7x<Z-`( |>;%>FX$I:PSO,/,nAraaF$ Gt,(4H@ Bn0ቖe^c61g&![󦕮mшU Z[[c(ӑdYx9X"t\) A<EQ:Lk$yi/:Űuڧh2Sex_v ApJaN&Hg2NCvP[p7^r4@tQ+++899sB`Z-j5aF8==E218CzlFp݈z*&{ VprN<|FH͓$지zFq&>DQD( /mllyBa'O; pg<9:EZc'7{5X⋷אַaF#9pSNj&˅Z4YH$9:2ˡ^YB kipe.rаe]*kZ^;3; !Fckk 񘽗iBeYn 4nvf/|4XYYa/,,7 znZ8>>F{If*ost]5(foɯlͻTNG+V'@U',ãk~ #9E\.sy)>|P<}уo$ᴆj Q9<Id2 YM?{:bkk =b((J)Jf ˲LtD^n}7oD.|>Bb9 "'}kkkh6콦M!54|4 ("H @4lnnV?d B!t]x<hضh4ʛIONSG!OJxJl6_HRHx}8 NCuܾ},f{0%ޗ|~Є,HӬ8 @$;$ NJ}4y;==eG08E$DQYN oI+ζQ(T*TUu(  iDQdk|>G*bUNR<~?D4sNEmxH|vQbQҗ^.wn7'kZ_yؿ>׿'ҏEQnWz!2 f"Q8n޼ɶ':Y~P՗qZaY˺utt^xUłETbX  v p^xe0MU]@t?JCs~`JI h xڸ: pj@UUܾ}1"F.uxP(fL&Z j,eY`@idlFD"v Uc>dx:Y$N0aIt:4<< >ip2Lp(X,˲08/pl|L$IBGۅp\ly$S8“" t|v3f{wrq i<ͰJEQH$ <>>yO}B0;~( uYF[dk۬$F8::b6g\dnje94,kYfna,ˈFe- JDPׯCUUV Pmi8==EPid2x|>n&6\.LD"`t:zB75$O&EQ0xdIRN:m0'" I9!I* bG5LhL&l?%C6,{ hx |>4J HwP()th O`0x۶Q,?O~i^4M'eh4G݆eYT*,8;;(xWyn?\]]E"O[?b39]OA "?ct]w]It]fdw`kV.r .,^u%zսAUr#a&bTUeOd2aO1Ii{tt'v:fC&,h54vyKN x<z]d[jj@iM*?n("ZbkkU{}d2,k&lll@ ϼ}6ۅHRO*JMj&4MOc~}4Mt:D"4 N-!t:vEQvyp!IA$IlU+%D4 R"NB|>MR(L|8beej5,ceeY ׯ_۷j*PJϳ `ш |M   pL8-,mwttaHz{\mf/O߸\.Iǯ}S]ע(Kz0&)46[AX\.~& F#VUa4 `ѵk8fj߿\ֲ.X- Z@^z7  FeLOR-`9?Ҧl0DCO$lSSAriJ4 ~g44ejno oXNLjv! ò,yʕ=5{MZ%o|:{ǐeh)ƋH0vIM|ᇟR $ \.uD"fli6_ IDATaDQlllj䄯ANY#01ѣG N%$IHӠQG|z|EQl}0txxyx!eY ?F .IH$"iHb6e)i(%T ǘf S$m#"b^/~[`z/~ѲϠe-w?Ҷd4hs;:79Jd2ш7v;/#Jamm @eс@V|,_YYH+˲8.Lc2pz=$I\.'BB!}n$n7oHfclln}'GFy}Nj:a0s u8eY2 >1 ( 6663 !IRt]g6m J*j>OOO*mj nBp8DTh4bb$aI/Ҧ):' T*ΪWpUJ%t]L&V,un!r\.ɂA TUeOOO( ?~I@~aٟNFl I&6Aࡃ(H>oj #)EA0 b e+t]GTb&En۽|ybxa]/o_U=(C+yx t l<ɓ'X[[s|>+VVVcd2$\.nt\*[zz}IO> h6z*Zlf`d2uRd2WTH:o0Nth TkZd2ax&6#: G8CVx %Nn$ |>8BN˲8AA al2PѶm~>ɓ' f /lԒT]]]EaŁefy$ r9dYf4#  l\.v=dYshP'j0L.rnXhJBѳ3jv4SP_Vt:̓"dzb\-WZ홷ΰ՝]?e_^V E\ 1NQH$PGCjpÅX$^CeR)u"\Y.kYe-wŢͮhnN m=N&< l6$EAV.|O<$I~"]aXX,:5PFRP֕jWDx<ykCU,eQ IH?/w/ʃyNAHEz=躎H$˅`BfNj a:ƚ'#CEWU(‘y$pdɑ4NOOYH$QXNSl6jN9"6oll6 Ht08ojY.T4l(IrXW*K6͛7qU$ ~dp\_#,BCRrR&T*L&efl:ʕ+Hҗă ˲pvvX,p8 Jd2[n|X ;*B sCm7s2a{)TUe9JEQPבL&f8a|c1*j ÀiftE(Y٬o"ԋ/tNP^/($ITSU2 sVa$ fdѢ! 'm[X,V~C٣A(4pr,˰au6ҟ_}o ޛIrW/<|_"<53# $JEQ&Q4 mm"ƛȓ6MԺfDZcsL3HY@PKFfFƾxKx!"$6Qʟ E23"<2{ٶg|~iTUq;<5_TUz=(`Y]܊H͛͆$Q$a\.cXc4 ~>S>,B( Nl6tӟ 8(!WU;, mo] &yZ a4M|l6Ԧ ab:BUUrf%t:#H)=zD\Ya81!0ppp@5cXڵkyR `39N~yޝZM?ѿB۪D l& )m[pt2!0m2JZ*r:8Eva(qX%$EQ0 qLRU:hH9S\f)HiU:ɒ'K۶>frǡq萜~$ĂXœJ}2',LrړPT0 iL$:={@oԶmض l6a* lۆ(XVeZ"7I|O\ދ[0 lہX, 1|?X.Wdb^GtXZq>GfI'?X.( u [t q2 `A.K4b\p8AJҺiUJCxLC=mFđsuuͮs'IAټZpyyfhDÍjEfY`[)9%6TE+ g֍J/8rzo3 kKLaEX&>$Im}Haɓ'z+b , Ar . ME|ѿGT2J׫~[exGiwifTUr A˲tt^]]A4WWW0Mrl7nܠ4fs{ XV~:NNN,z!Zo' Ieݻw;eu(Bm Àun֭x1>'Z}B4 jaP># e@EXD.CmjH6^Պl݆a`Z b\nsFP\.(ܞ|w:YKM /^j[oZSJ9ݻwuZQbYۡ~T*x^RQMi yz=ܾ})<σysrl6t:hZ8??X,qyupGu!%fr99}~z[JJ4:*s(0=1M6CI~7N4dy A( .2 6 1|b}@6 >ukWU''*,~Yz I[X^.#{%0Kxq `b>(yQݰ%zrm0 j5QEU!K pbxBY|>8/~WWW@Yr RtT*|;{3UTRt=vlVK|Tfle{#i9??zeY8V v5kyr=$,C>>|H P0 r9͛} `oo0 ***>ehCekL&jH X,* g"A@b(J̯==qRJCVNU{"Dl6$IaZBE8C;w0 t YEA,,(b6$;eb,Aem OᢩR='J R=*, l::0!Z-(x |>͛7QVquu1%ytcضM}1۵JcZj>'A0 5$I"5k_rib2V8x;r|dhP3 XWXZB p]S ɓlySz͛G{qzb*UTt:K/ҭ?pVe11y"4M۵KBf5!8$IN:q EQo8Fۥ NhL&CIp6yiH*zdfY|h4:_7 HR='J R=*n~..On5Mõkppp@T#<^?,S5[.fl6CEW]׈eqg3ض(A6EB694fiPQ(lUIx(ǏS\\ǁ,X,j @ѠCE㘀YoFTӷ\.1EX}c<}PС({*UT~9tKZƫLZ`>}r q8::q(T*+YZa {(k( P j P5|>'A2O <},Kpq\.QT2D&?߲,IM\/v77{~Zg*stА*sp}[FqM\^^hP/d2qHz> RU)IREku}pA\.GaP4Y*0 l6q~~۶i y0MB@QaHt~q I&zRfuN*UTu}KzWued2yt:0^cXP(@$r,EU 8F۷ T>5,x'dF) ?O%8 Lt]d2AB>0( u?eG/qT>aTRAU{M DUUa6TU%ǡfquu3DQFf^G{Vp]A<apyy` *%|d2X.mW`0d2i$ @E<},|eV!u]pX;X,0 x VP~z0'LNoA˲dFuK(jgxSJ*?З[wX-WߨVj$IIlj=zd0 !8, BZ fZ ]d0"kd2+h68?я enB>GE([j^>\%80MrEA.˲㘪_(R~QTϟACTwCw[6]z41ϩ0 wr,$Y X-WdTUja,+"0 C'&4v]$A$}ӟrf2eW 8 eEQAa;?c0t^Va<\.c:S$lyS{d,wTR=sNgNx NOO!",& qd2f3l6JM0 Ih4:...7N`6%bt:}65OGͺ IDAT$I~l6`0a&6 80Nd2H\~BfJ Ƕqxx~__[ieTϡACT~?pG_VXVL&iqDQ۱w,d7ݢ(P(`6w~w|Jl|i<܈|eYض qp`ZBul6ut'a90 ۶!TU%eYNd2!dLfpX "mLJ--(BZ[nB" CA; z,ߐ*UT:n7ۊ< ł֢^uaYF$IP,~l Klm 3%t]눢R m0f! c^VK*0Z86 9*Ah4BF:v>|tuAuq`;e&~u:5R8rTϡACTnk6`Y<ϣ\.RP˂y|V! CLSh4Mm, `[%"8rD&",KA$fh6`c2@EqbnKZ <σeY0 AuQmrVdYp۶yNNN1\%%A>'Erv)6qqqdBN6V i0yGįs+.<*UTϋNOQ}KшIy&&jHq~~]ס( 5:0:r& jU*o6$??a)$. UU1ȝw~~EQ27XjlBt7oR\.Ss&I|r;ps7HOOϼô2UT!UT`xO/Ȳ҉jL7 ]m4°LzFZi}6...ZA`&ݰ]k֒O} \:A@E%;iDBò,0Y<@QkqhD`͆V:`YmؒAP@ebY*)0xCUUiڶ>ٲ,V+wuEQP,!" c^,K 0E`N.r&L$أiEcJhJB,d=f}`C2 eh4~s8νTSTR}Lj[,Ln5M8??8y8f fWU*P9 9^x<|( yr}zz~OmEQj05Z&S$b0lF':<ömȲlEQ idaaaM&02 cRߕd$KE8;&DQ$ ri%6""LFToܸq.|:wO*U5|V+Ly[y\~#!# C=)F#ܼy!t]G&p8,0 `®yv~( r%$I%>$ A#"pGZZF9XYcA2he֭[(Jx|B9A$I/I2z8$I8>>l6|>GX1t]$I0M{{{ DQaKژdY8(W*Z-r9X81ϩ!%Ǐt\nc801MZ+,0 >F:LӄT>1$۶qXסi0d2l6d:_"9)&l6K܅Bjt Ayx:Vw^Ey4 V ^bk׮Qմmm_߽YJ*4JK{QdӵZd2Lmce8;;#h*Jm(bZ4M@E$ /xUU keE^z%\\\eYq I0E& 5^}U8Q,1!",˂yPU''&AܒxbMF(@L&C'QI&vo+ł >,˂FT)Us&W^$IXr4 ?9dnziz?J*տDwܹJsNl$vk4l6EA{ErD6~:Xɚ eYPU(R(Tw)X+""E> ƓV۶QTP.quuY!5aa0Ky"cضlFj uQשk#QFN56ZOY XWiK*U:hH*Oaᱱ\ouDQjeX, I+Y.6 /nKl0 )ɲ,>}+V^EȸX9%u鱜BUe|:N'yanX,u,UTυ^}3o΍,Jw`o0<"prr(d0ω0 1ͨaɰx\P8i,*<vN ˡB$F#Cer;$6412v͕JՊ֒d>,ˢxeYpR8 , JeYXEѐBF}\\\Zb6u]ym=o_SJRJ3j0; :" Txvvd8;,JT*um=\.Sn0 y %a2 @yZ (+ɝn6G. 9\.)dSa%'Xɐ$!"cX꺎8.jA3y...ZZh 28˲V0 jY[꽑*UTm6 0 #[^cZA`0PDbOn1 7`ݐ'0prrM?Ɠ'OydYm裏5X,pyy q^u?J%f3(;˲`ȶmt:`7)0  8iD *$IBEu]  ,S3][,(ZQgqNZg*U)4J꿩/|7V˯Y%&`,`[!h4+ڵk`Yxm&]EE&t:(h0<ǡHrTjh91 Ix%YWe1PqxxrbTP(lB`eD.Cݦ{ pXċP`diBu1$fu* 4Т3۞VΟ>=3|kJ*'F&e; #Gd&qW^y{eqrr`fu+lQ:t]i␬ Ejn޼p~ON6A(E!j G`YlF>|>Am۴Z-Ӵppp\.G˄YO6Q.i.dY|>y~:9O[_?oɿ#RJ/Y!UTMݿ899UU+"Db@raRCDb_X.h486-@UU. (BߦA˲A(Gq Àv "Z4:8Z<:j4}B b8Yŗez=( 0Ni (.²,DQD^'V¤Eшhk40M!q~qqgH*UOFjh 4<^Vd28>>˲X.ĿI%  _|EarIS9i c5 ,غa:quzwAۅ눢|1Ly8r!Qw΄,8jXcXll˲Z(+XLV&hf\v a B4, V0 w1?}jQ5fX,0 ,L2i|>?-L*?R:hH*ϥã{AmzE^\q=l6rmZ&p,V-`rjƒ9b 0͈m&DQ]ץY‘PN:E3& :A39zd2aD7M|8~پ3RJQ^{nw:9\%\G( x ?~ qh|֎5g&ǁ(@CsYinY|E8ٌ=i }R6 EE.>$["yLɟuϹ#kD, q`$PAL&0 CxGIsyp\y`<""<0 e?tYJ+4JRE݈v@7oޤJmYf!2'%"a%} T1ٶMpNЀGXm8FTd]M6J$a2la٠`8b4A@UU?&pVgd2<P<ϣR0 d2 ByrzDkalӲa;.UÓgq&Gf!JzrʷupnX >ưty68 ׻@s׵!IXb1c@Ep 1>|{P.i!A*V+w˲P76(Bрaj50`8NؽEf xLf k3%lF& p@Usfm(<Ţ0 qy+q~w1N"A ./Q(, @^'Wtv5^@xnb^O˲:nSXsj1t͒aF3ܸqF *<=gX7\ey[wuuL& 0ppIRE./w7 n޼~UUk,m؞n2,l.}ƚl6bY1(a&1 P;K RbH*/8 ѻT*( 7ۋłHd$O  Pyrᣏ>r [AqLVzrRD&Qh4pttyLSTUjnhZL&P4~qX,b6IVF, ZjwPb`0뺸y&A}h4qttDޤ2l<Cd.al+>jOg&F>#aPV80fa֎YNi!O>iv{Lrd2f&̲YȲMa0 e0PU \cX!Jbb2R`\l( Ӵv!0M,#e xd8^#'q&h~1& U.$I{ͦm\~gM EQ^`wyGG(ts߫Tg2cJeX0Y^UU> V6 &18Ew۷?]=lqP(4MC?p躎|p㷬PVqu'~ZlI!xhs$ƍSf=xld, ( Axʨ{8`2a ~{_G8fgg](߿ ,E&A`B8`8!X)ya,v(D D\*c4#֐enwW蚦f86 pUU ہ'ry?~ ˲1L`60B.blx^>lE\&&Oz|| UUf9Y_1_d4t#0 C5 }xG߲,0 uQVwC(<Ȳ,+HQrz=qjT*ɓ'Tm9 P,SPų я~L&CCr qP*`698F\ xwqzz\.fCђ$QV114Y @x<ƃpzzRD ~'W!UTUщTRnR>4M " axy\ץMZug],BӶUYi6eKhtiF"LӄeY;8ICrl7 à\.#SEDP&`-uX?l6hM}Asض ]qttD8Ҡ K4 h!t]A8FՂid2zbzeT92׋xn dzciLIx0(T˲DzU($2$ӹckhF0Mv[f=Zs:;fGNSh LFW0>"LcNÆYWwz˲FńFm60MStC$I'+ t:L->`0txVKց`eYC  |>1E~/o+ 5XubLl&d|>p8`0ftffʴ ^31d`0} ׾ſVMd2Ɂa, j '= Fg9tbڲ,ŋp\t:rvi@rժ6ylZvH 6bX8;;CNj/l5dnWsE8tj k" t;^/6  ٤nw|5?:.^Be[Fl~4R;0 5)L2 p8L2…Ag3;f0 $Pn %LJ<^ǃJ"S@64ֲ$7d2A l 1SUf5~hT&qJGGGPiD"8<<Bjp8,:X|>?UU.a٠T*0 ihV\.lJQD"H$ȩzd2`0z#ca A\6Ė3؍jB.烢(& ;o1l>G"B6r,2F9N56728Ţ.KOvy8NצipZ" Bv-vkY00ͱ^f$%Rp8L&#frfݼ/lr<1\.X,0 |w<ۉ ݮ-b˻pxt:whP]G % CI!c777H$%Iz=b1o`r P4ef3l[a|'c@ H$ZH$hZ;\"(t\|dvFEA #@@މI Cy鉔H$s?y `VSz5׾?o큆}k_j4Sd2#MFQ.01LԈg[~?LӔ-&f381<<<ׯvp8$&rZϑaY2ө4n["70lRUd2){Yg;9&Iez ahHEDZN}>* ө|~UUm@&<x?ԉGb[oش%{0 P,DW J˗~p:㏅-CJzFG&A"9'ݖeVAQ1cSnUU>JBH\n)ۭ0:)YfzJL^ &ˆPTt:%ŦU^GPrIsz1w8n68>>z.`I@דC)]~ ^5Jݻw( 8::|>A>C!F`Du;vaT*h4bL&|p݈D"sV<(UUw}'/  Il`<Ǐ~#r9\]]!l?54zbxpp >2n[(򫪊`ز}ƢQ8>>FX~3.K6dp8-~?=a fQF⇰nen%^˳u,˲N>Z'өx!gٔwd"˲{,b0 ~D"' ?JVE`'͆a!|>D" ale2Mf^r888V4uYk_h׾k'|v>/D:&~30ЊIdZ\9M9#-ySUtl\.@ V%&\`&R|)NP`Y4$M\.B/^^a;8;;`0A21HDcR)Ix>z*j.jb: N  N z^\.l*wHd2LFdDQugCϹZdNNN0?ؿ^]p8I,R5rZ~?^~-zJQL6`nv,km"LZ> mFv@(Z,R0IظW*8v d2h8b4\. Si- ix<kkȦ`sEf`#C8aZPVaY<&^/NNN=ĸY^h4BӁsX.CGE*d|>Zʴ G^iAFʈ%ƣ1(6 4xp(dNJjjTd],HӘL&zi1P(f f U*=f8...~{]fB0#vlj*J ݝ06#|GXD"Mrf4MXXT$dA(T*Z&4`eD$%{;!]ׅGPK`ܛnt?޿|>/p8@۲,<~p:r?SZ-Ts:@uuuf׾?=а}ת7o 6nK4'sFн^Ԧ&H$zXVFh]w+dYV+x<(wޡ0 ۴ Z vPHH<@QU}PeSinϟ?xG}:GGGB4$PBM1ɴl4X\n%N}N eyzl[qV„ c/Kkz Oʜz{Bn]|HftMÿr!D6'|v-xs8cI, v^xޡZ">#ϋY`0rD0SR to$ղ,d2\2Mٙ@4Z-ql] Z X a4N>O9'x< 8KyK Ǐelxt t{(=Á@ bea:b<˳xww3f3L&D"B!ywe2n6Ex-NSET^v#Ɉ韮)~aEQ;n?,Bc:eH#L޿p8 Ux}L9I$؜e{ \r2)%"z<\CT*t Ӂl ׋f)Bn["1)[RUUb.&B^ vbfz- E{|rzO'? L/~ q1od2A&M<aK0p$ز\b{ACڿ aPVp8di߳gdPh^21zTJhl\]VC)plIԽ:T͘4MhDzA- 3c<KIY>oT~~vl4 NnG>rv !;~_1Zp\¶lЊ~*ZdhȝHJUUl6CX("*Y>n23 x a|bPJ3I~G{r@dzzPHE!0SF_!z_}F`пL$Ҭb1f3i*?j5;na\.r ]T5nPE`<)8jC|-K5 a%Pc19 ˥>%&":nooeZM;EQD_Kl&tv EQP. )Sb61@BZMPx՛/b.EIZMh4P?M=izG?h$ }NNN|ll6oxppp l66Ce\b0o^~M!mG &H<YQ8i9֛ ^z%26lHMxth@4J%$ N^Wr-YL\ @]ׅQB41ݥR[4&n~^OLg=А.Q6 ͊!m<G:E΅vE鴘V*x,|2cD/;ցƣGh4WWWr<;Q$}xȚ4MdLtL&SZ-BAdsY GCl-8r~p8lVD"zك҄>nzfe":NŨ)`0(v˦PL3r`/h4ƼX,v IDAT7o|u.Lp|rT:^u)oۢ9lRhp8vzQT$2J ?d2)M7|pR$t nA`0'zfbl6|>p@s0J&l4\xz5ͻ8j9K Z8Sl#}l[$ C}>dw=(rl9&,SA6`GobkmZ?`~&mp( Ɠ a~LnjF`GvBsƇq}v[=~_eͦ]}뺘KR2B]Q_bZip* 965f)ԟ u3s8­{nf00N5p( ҙ4\>jũ`qcá8`x a^\.dYmb1|gH4C»w$gowS򻻻R@xvmn`>[ Ob4alp8d AܘNgHXfSE8L <}WWW"KRp\fx݃ |rZ65rp~Pyx<.Lz h&l̊V(l6}ܛ,3*RG$\.j5wrDTPF&v&sW*bj]-J1q*xpttX,&`0#׌|nMFMHqrr"|V&H&D"x-LӔgtڧY ҟ1a"1l6888m{:F$iװJ5MC^f=d2Az. թJd2/&|Ni"HX,@UUiyf3.@Y0Xp8, = CNy>={&h\ r,KRG_|A@iYV[o(-HDtDf:ʻ> k2+*nLS$ Yz.zF<it:0 dR^PT iwJ$wǏԖr! Ǐ^mlD*Lh۲׫ժ |899Kz4\."i~7ߖb}۩=а}7RO&|G`~^f&YrK4B\טNxP(ϟc>h1kfSL57hgͧ*+t*D"R^~nM.º`׋ZJǏtZ&,|x<.pzkJˇXcwX RIhLVp:Hx=á(hVt q̧fx<`O0UeLdݢlJu4R#8J4$EQs@QJ%anP~qzz;niIo_VH${~{$!NT.nj!Lɓ'? G6 iGV*5>h4Ɗ1Z,/H`6!c<Gmy<t: V+iԇ!Ѩ0iF274M꓍L&Š JI3}BveG\cUUQF󍠟 N^FC3 Q\H$"(4Z1ǨVbJ?Jk2L&Pv :<EA ߓ3Axa>aAUUöl(HRvp8l6fǮp8bxJN\n4 n.o?q׾~ ׾~k_O&?Ţj*_&@b9G@41LH,4Bɤ4GGGVPxKWp\HBxDi|>[&JH~7ͰZn%izHDqzG(` *`ZI3ˆ+|E9LT /1`vˆ/_i;3K%ƕn[5 lf/x1gۍj @QRӴSuVRQB A.CV0p{{,K/x왰0tym @C)b 9gB2Yק ͒ s_s2np41 ApS,r9JOtfeY@oVyif  F<;~P dP20 ŐH$"3LRνO@4Ma5ob<r]`0@^oʊTU $XLBaZ hy r$Fv6!lbrxx(2NFt]st:.$ u*0^PuS~h4~h1(L2{4Mc2ج|HjUp8D @TB,d2A<D&bchŢ&(;z,Kd2N s ͊cHcOvK|$_gcd,>}޼y#0@Ev(1NNN0ﶳ3ay<aTk48<<J`9ij' nQT3/W1{2H$/_~׾kh׾[-nw!# N&R^yx9}ccG88 Z-4 x<&(=I|bL\.L&e"::RR)ha2FhOp8pttVcS뺎@ nc(h@%I2l 3M |S*P*Ŀ2,-|Z-{+dP&Nw<ز4yZ ;cr_oT{a_og )zT\.$I/ByumE |>G׃ع#~gB:'phfL&UUm:.fU䵐K=vE<8£#B!Zwzzjgrb`NE#fE]TˇX|>sl X,?t:\Z&^]C;L5h4dL95fwO9(J8I0(t:d22ynp:H&²z b^hj\.z% 6 H3hԎ۱0"YWr[J% .\AI5{x0q?_RvtĈ 9į%\v K N01N $` "lVM988p&ɺ]\\@u~ZL(ɐ U(`& HU<DvZ3iu] #C 8OMK#3d$,,4Ma~TU)s:"ok6[4<{Ҳ(u\.Ǐ=Bzz@ `}aȀ:B{*^L)Q.}~7ДT* .Ӆp("_}̐riz -0 ꂮ@A(Bрj""3 koɌw74kǢ3juJ׾h׾[FӏDžtGq]ėw駟J3Dvz(B,D,r'yÁl˅l6?2B$z/!L`ut4 /_vԓJ0N31dd?FXDZEC"b`7VJ@Ghɬ|}}CfU࿛Z&ޟlb~B賹#<`7JEX"JEd |>|>9Lnq Բ,d2b19Psr)ix왰梊Aݖt|ZI3IY ,n]CJKzLS|>X$޾zF$AB<ɥod"^7An?=K4NVzft:j*&\~nar,AP($ e,5 BNt:vL`h$/ɈSDl4zbgi|xH1(I&\.̠I*?|3CEf^T*%b!@~+ j` tz.b1wc^d¥r%K(H&Vp8 Bh4\f.;}81pqq!tZ 仜.~"H ()*BF vd.xEԬѣG, l777Fp8=+HDJpuu%,Ko(l6P]*Bf|>+/,}Ƶ}N*~mYEUULG% IDAT>';)Fvbb2G=,_w{6lZ-4M`Mrdk4 V+1,PN4u}t:En)H8 ˲0QUU``e lv]hh4[c%'ټF{R T NSh`l%z-nǣGP.?Szt0 >0#RFTUh4B*`D"!p(4fz]4Zp8 j$a ǘu:LP,K*`h^gggF8;; N vRr6I'eYøcKј1"oz8N&4\MN] ~_|++[Q&PDps^# o/P,l6ws.N$$#|.5Mp8aH&hmV28`X rzam6BA),h4*i.r ~dn]{xx` {jjg}7=а}wR~/ ZhYy}'|j*q:szʆh CqO5x@!r5Nd'y 塌`4c$5tZ,fjbhCV+K&۝r!Ǘv@_C\.Z-.zv#7 ah$p>#h6at:%y  lBQ@4>p}ٌR 9DZx;Oja`0(D<'ld2p`gϟ zp:E@6H[4vA&% O&~BŏD"nh` ߏt: 0X.XVq~~j*| <[f3eDQa8("L&#TvJ;G[}Vl&H 0TUDd"f#d2jzԟH$+Ih +q @!aXg\"!Fi   8qzzvh4^'dt}DBX<Xdj%=z$1{_z!I]|B eW9tHX "QUU!l2 LX," l\FPXDo}J큆}k_F_LJPUnݵwpzz*@»wLZ0S, )D"(ÁX,&zyFmbg^C F<^//~ aiAv;bXAo2v\JSL+w]:vkѨL2N%3&  M@Px<q???xri~ f,gOq:= SMh@r^W`&^~l6+o˂qVUa"cy$テ\ƢF#y8&hg*. BϞ=Cۅ(OSD"miV+R)<{LY/lV4]t~~.NW0Y{[4%Îs@鐢(ð, z] 6I'g@(;=q_c~Cuɟ +B!l6T\zFI-/b7kT`eeq}} ]cΆƇd2~䉀pD[%\6g4 H|>#86u«jb8L q{{ MdNNNDjDŔg|888vLH$d2 4}8q}g4Mv6lJ:@x~]?;;gnc4 OtNgwxp_ jŽ!cҎv@jZx8~oŅАJ. (PڥpVq8H$z7Mh# 6躎P(`0r %&bO?TL&z(rMDTN'≸? `Y,jJ˲ս(J$l0f\7 N4 ^ώ=CPq׾~k}NǓoD$-Nˤ4]<! hNǞ^/lj*uϞ>C ACz-z^nA\.'gj)6TUE:}Fj5ѣGx- h4i ̐l`LCiIj,#8??\.MӐ$VA4itEgMSc4Iض^]בH$FO D(B.8::5A vX, %4bFyi@i qBn4wѴkwo߉#3vfj9٫Vr؋F" > i X(nrPU7yr侯}ObFbgPcw[lIZ2rSܐ]93_{z]8s\Y4fwuU091%w/vWEEn44Y29V"nnnMd2Aߗ =ˬ]vu]6іK6|ڐ!3 vpK\VI x|` no^@UU|8??GxRxXrvL&l=99`0$#?J|ժrЌc6z{P(˿vF|;NG"!tCecDQi[h6["4Bd2UOmx1 f3hsSװ4Mz=R鴼ܚL&H$jxrn+r5ͦXuCfl P}>+ZV+;, >ֻC(]__Û7oS4 D"2sEl6CQbXm@y0^XK|`1CL `WP`Yc6ӱ^x݈'bp:۴D"-^\.'uO&DQ J>aT~?B[i"_nHXnOuξx01usz*  I#KlVga۱X-FH$( O~""]>:`H atzdBWN/4 6WWWpZHx X.@τBd2A>d2G8F˗/<\ĢH$"чl6W^IffrP0 . /^T*%F'ND> Orh4x<[fSZ-(@X&l6Tt2>"JZA0Ml4x<^L:</ chl߇D"/'GC @˗v777F0kxUd+cX^TRix29\.ua>#HO< { u) v<`n0`VX~.ޜ#I#L`Zm񍉍 xv`&c@ `l~FCф7k>`5/_~´r9|RE/L縼G(߿}z ?㡂0zl6+^J }XP(ƝzcAcウm;`YZbQz-zznx777|89yP/>ͦϦSč< ]EE~ZI>TUgdVPT*8::y:-c\iR9L`ժlfyuO~ӇB!8NxCG<G$3~8<<;#Pɯ JE"f0mrt:=6Np\x...zϚV b۵lY jl\`# T*jc4E>!Zi%"޼yh$*>ƶ0!?L"a<16;t6TRѨ8M 9 0nF+ t]dY;c>Ã0ʼnnjpӉVcF#t:!Ə=zj*m"l|ꫯm Yj5qhiqEV+Z-n6?7r! v㗿|~/BC*<40Ͱެ-ńMqĈb0H6xkyJErnLFX"l_"4L&<{ _5޼yD"!Qz-bq`މPb<o0>p8X,H$pyy)6zFZzqyV"vr! A4e܂V FR^iCt:E,l;ߓF NGNGGGqrZE~onnpS?яxDHRCzFC͆V%. BE ޓW <7~_܋а٬|>Z4"~xnUTqnppá0zâ E8,ƚ_XB5Xso 8>>0҇DE|4ſ~d ?ϑdĭV+V|>>w:l6$I n>ȁH$mG; xx,YL&e> ɆNSri⫯ڣG$cN7?8[V%1F#aK puu%nfslu]?-őHDn$4MdwZǙW?C#v]bQlMl" :nnnppp 7;%ZX4Z-:꺎p(s ÀiRC~?^/4MC*6T*`밠c5x^m[IPkD`8naշ$f!዇fH$r!N.jR GtPtha:J`2C IDAT&t ;uF W` ȫT*l6x!٬XdH0;9NG"|>f3jn[ |>K '[EQ( ͦ|1Fqq:蜠3M+lv}G6Xp8غE\.J4...D0\,n7"4|AWrAŝoVDcm fAUUi!+4MqR)x⎣Zx<YX, ۿd-w[Fuib\8m;ی}]bt:pM0n ^+l`YZc5k^?0g\. S]lt:-yÁT* d24M\^^"9Sd` n-ɻRnX^eeJ +yhxn[v[ĉhV%#ny8jG(].<l6j5ɤlKN&4|m6R>_|. >j@v[ #vVfUUqpp l4 |^v|M888@<Aode9- y8Nu*[D"!M Cf0`+XV!ˡT*[D">NP=ၛp1ꍚ0CXGl6C$8HǷ[]h0DP1F~"MyB>2 ƣ1l6 c`d2;t2JժGEo޼,߷t:q`ǖL&tH$d D>TJ7)lmt*vN!`Q~r, }.6P0VnEA\=x^yC6R Hi$0+alJ3ׯvqqhFgiNNN;! ^\t>v;ntf A8B&)1ͶMF;;Q|1ƊS֌~_bT=dZu6 E 6 61 qӄ>" a!bcXztYC~nYZc5k^?jnV``r8`DtMlpssoVu]J>|[V]/r 40HAT*UUq||,wIv`0؟7$H$ve;H a>BѐMiX,zRiPnooy0f>ln~ah`0!*HÃBB @QPCyx/h4HRB^.جג&ˁ@fI?s?>ζ3+c<c2@QTUl4OyR>5l6<CJ0 17$+t:a&N<,';Yv*NNN$>NE b"8#q:!L +0MӔ{5hr!#`0H-'!vRIj!ZUU90!mϺ!* Iibv4.@l6vZ@\ nWx~;z|zB"HHD~4E@ׯH@tm6t])~l6V!Nlz=EA yBP.ERIEF X8 _ބzu JJ<IA$((J"zX<[!v~nfr6ْB Ag\vID7X6MS|{wN PnnW/_F|Wqͬƚsƚ{7odBqݟt{=8N@UUZ-y$܎t:@ 2}KS___C<d,el6D"\]]IӧO1LDfh}>n J#-!m#x0xG}T*f)l899m$c aB6PH*Ig[ :m߇[/K9{<^Qb ٳgx^6D"!w,C0(Bqt"JV%LMJ$B~l6u]8dNmd2A8F"@4Ljt:eXX)QE!"0oDS CX`,K,.z.11W [lFQeDٔBSMWP"jM8AP@TO&< 9>3A#k<=9?[_Xˈ%F4fL /T\PR [RX,JJ8Oё8fXVaes6E(aQ( D"$IiF 1p%!5jя~$_v[.f%rV5X+KhkQ}/b_ۙmlض Hs٠T*a3MrIƖt>3_«+qm*WWD"fH~_ۇHnj%\}͆x,wnEA+# ƕf}\/ytJL&p8pv( 0M>Pd\.9Й n1ͤV1ZJ;#3@6tZ"8t.Oz}w1ê^]cR)S@Qާܠmt:L&9hS@΍rf)zʦnsA0vzRSɆp8cT*yF6ܦ CiO`L}n[4R ~;p8z]q~~c*&=&=wk}YF0Rzje/%(_=:H ) 'zt!P`0^Jd٠jI{4νF7 3ާPF#uEi|㋍|/v4 l6Slq8"x+ x~hʽ {{h\b%)E<_T.n `u:xwȶ"F2a%Kc u &:cSUQ~za%V2}4Ml6ot .2pxx((\.||gjժk~c Xcͷ2`nfv15k%p+p80tǬ ={So"J! vqP8r:d~ 2f\.'txO v-sN=x`s>lʶ[r}]>wJuȽ`9sɓ'H$0NQdcOVB('HF8 Ur٬^OMTJl{ T*%jB&l^zrP($|>t]I>lnpZݎn+%(X}8 @5XV0M6M\6,bt:9Pibg2sdWuzpBhx\B~_ BВ$i8c4{rѣG4 _~Tovz="˗@w MӔ[WJCh<GߗϜpr|>/@nWr\V_;bPDPӧOnMB+\nl6OEaL&S*+GzTJ>X'"Ƚ <8>SU_:(Xd2l6٤N|3M{{{"`ktdn١\. 3׈uCOLѕBEt<8d;6 a0_nCQ|#<88@Pתke,kf444g!0bx`qssOrAUU<|PdK<7|ber)6e+v~EQN|(U|6! ^RJ6C/}ncnnnd08t:E^G91֕v\-l2$NNNӟTAE0sRaGWJ\9lA@F]ױ6 a|ݎh4*>S@"4 H$"hǍaFvxa(XvLB+{ݖ{&\.ٳg4 i%J>\.c2H@u4 W"D"y-(Pzfw*>Y/IFf0f릓;;Tj-mm d2RI^۷o" 3x,7w:\^^BQzJtЉ2qzzzwM&"V.0 iqӹaxB atMѩ8?V:HM8x_Fەߍ5t69l6 E&`0@6EPd2)}XnK܋987Mh>߽ ө~f)%~6nc!FY@l6#_x<.NtD5ͭgLr( 8V 5 v[-!DX,v5Xb@t\8{W{ZvVQוh49kZuXc=%4Xc5bd2la7<\VX,䁑u`X m4# Ðj%i6L&h6vz899;7PHd;<~ۭ C)'|=ݎT*D"][g>kGĚK`!3/_+B)4mt:$ Z-|>>~b1MӐ0PD' ` mnzxVm)UU7 h4=z Nwb%x mK|C'fUUE63F`P*EO>-.I\o~v+z^9H1B{V烦iפPb(@ 0>rooZMlpaK4']_NnW]1Ft:SBfn}4hZ0 B"DH-VsX\.E@b|Vfkbم3N:Elk0?_^Ǐ9`z-W>땊Qֱnb1Z )rhg4#v3`ǣSP(@QDQd2V}Ȩ けHDbvX{Hw#;-Gt\s~ƪkm,kV^wfQu]G6ET"Օk&vUU\.ҜH$P,W_^#N 3ƹ4M" bq[_xnlH$d"x '''\.C?qmV+<<<ߡ>T @@gfXy埙ip ~WFr=6ͤope0T* 7T=LSVnۂO cM 9aIu@Vv;4>8caKd2t:ETAVC6E".Vci"ׯED?K^d2瓺b6vhtǽCg . >]$ b2A񒑱l6otlc#mg ]wV HDnBA[@@uvb忼zunYZc5:`5|X,dF [*nsZv'X FlrBAIn閷lH Z*NOO+Hr:@ 'h5MC(th~h d͍DVTUaT*"\oXigvUdFQK%)_^T*]Fe|xx]qrrߏ|>Rv-np8@ bjh4F!`Átx `FVz4pD"f#ǏJSd6n#$ m-h6H14υW|/~!&|躾چ!<:BZB*a<c<K= h&lݎrahk 1 \.Dm6 ~c-ir$<"[<#MөDŢl鰹jNbr^/jtt:r߾x8GGGX-F2lFRslLRDx<":LP,z1ͤb8XEhgvN5uyRL&#R "*"|rSV(<㱈WIp8PRiE [) 0S޳777*jp@Dl6>ct]mx^b1LS/Yc2 IDAT5p,kg0 ٌ2 >3v9-FM&Z-ADm0-WWWL&QT3[{4$mF.sԸ4ń`b2#NrᴣPȣmqAMa 1m7,v: t*k-ۃѪ>k۝"djDpss$Iamd2ɰb1\.ɛ5x<J2ZD͆lt\.'6r6z~2ߧhZ(xء5MC|>a` l6ɉD E.P(C?D  VZYPׇYE$鴴[܎i`+Ȃxz[CF"@@@E<tG7]NS*?#zꎃ r} BC:ƻ+[^y(#4ьn+^cXH Id0l7 vlǒO$F( {a@2͍D4bX,&t:-|s,өLXi5Jov]"0dq dYZ- ;բFl6(hEqssfu/Bf9RߗϚt*lp¡`/^/N':/$E+}ƚVMg/~ixGt:vf\.gϞaoo/_r#01uڸ\.DQr9am1gkkb^ox-5 f>ӺŤ~ZM5#yd2 v\xμFr8RUUNsYr;%7 ;s4M zل+,lݘNh4zҀlO<8LƥL&sC(B*BG0Dt:*JFIaor&4P( ɟCx~{{{ϱ4rD@>x5}?!c0FX*lrlxضUUŏcx0 Ȋb̼_`[(c(zF,'|UUjN'l`f7@(،)[!~V ]v=JmE}}-0@ BbvpV%v~?LzZM˓H厹÷)h!d[ s`>Czpݸ0v%2@86ҥBAkFrLG_0Dd2 ߇iFp:z4|^IR"j5l6rc5Pu]?m^XfC\FلM=nD"(Jp8gϟ?,ƚ{Khk~-f0Ps<1 E",KZlwpmfCP@<nB>s9*EQ:X,ߏpP(t:Fd(6ĢqTfp:z'V¡f $)(vfWZ-1 oad ̍bLVt':XgND%QχlVT*\.f ] G:#dR|1)R]`In>Z^]̲Aa{ CZ;VZ&-~_8哗 z=~t O|n)v'C‘:QolV|!D2b&1j*|~<.ZvZc^c0Oˌ`n Cr9V+4 2&^t-aetavfS rc:sB:gTJ,dRËz]r m'v\9 ;:h(bp8`Nӑ%nO:z.`]$P>4M$E@UU}x^avx^ l 6tjCX,[дvL5l6k~hZpo^nA0uk{=zp6LJtv B(+.XÈNϷ7b&KB3z=fH&j># X Q.Kr9 CMn7r<:`jbmجЧ.W647 d2Lӿ:Kk[Khk~mF_$?F#iZYHpZ21Ϳip`~כ0vyyy'Gz[h&Qڔ$0ϥ^u]ix(xrD4ir`a[ED"!=ie u8䡂FVnoٓ,nnnp||,4Mݮ*4Mdk~a8!^W!Rq\H$wDbn+t{s[,j>E|.nzH$"0WFRf\VV0M?FZ BN4F͂N t]GXZ0 ukOym6zl.j0LpnJ%QUnHDy )|mSuwwt:N$r?d;Y1<(uʲMd>CQ.3Ad"@"h"5yd-zSUς@i4~_}H+9&&,˘N$):TJ%lu[Ǚl8z<ϝ;  L UUIax4/=~N>l*<:~fYZc5XB5Xk5xkMG+ $3.rχR$4x<.~FN"t:(PU777xHtc"A"|>0hRIjhqff! h\QFX.rt:X?rttxJY j ZڲoXE|~6r7f+`kdM#i `>N%^q}}-MJ6 @xqz麎d*=q vGt CD"t]x<<}J(<*=z$l~2ݺZhggXdd6!Hsal4 6M!ާ>L"k@ b{@pWպ;d/!Ĭ]FKv$ID" @R^p8]:7ip6N+=I*g [⛪-Cƻ.,v;PUoe8}z7~7FQ) ln?'mm!7 _@Ǩjw$0Fjiؑ7loYSڃ T,ȇ|8#'P( N͛7#HDx"kc7d |N3TNB;_4Mx<URJw^\' =}*|T*%ot"n#ߩF)E40I!$(xPUrYQ8X._ncUn'K4ML\zVA "bniL1 c+nV%Qp8,rDRGG~ ,8~vZdx<.ZӹἺl^*ne hHsluq\дeaZKt:.7q +dFvU:Y[*PwPUz."AVU|~':%Zh40_[uXcͷ:`5ڍ:~6u]Ԫu!H" عBqB;1J~2QQBFͦFhlq\=NZCCGXh_:l6zf#ш |oU*q 0:c@d\">#KRUU>n777[7r)-t60~2͠8vY|%zF2hMMӤ7 b٠nv#LXX- G07~m6>>?7U`E4i,r-nYދpxX>r=qKzX,&˜FA"ahmX1͍Pk#''v|F#l6~q躎uuWQI9@&*6 |>b<:ݮ^B6kb5X7`5d2|t:H$Ăd@:vXYFzN\nUnL&nqqq!Khȃ-IDaɓ'G } @P@VC v{?pX8il6r0MSN2Oe:$r! ӈ_XL&lZxܡc^ /{3^#,׉X<.0[ޫtGt]B!i 8s<؋(Hix!^zǃz F#軺P(dz|>.{lOn;3 \.f],HRt:ߗzRF>TUHC`hBbEx'a pT _|dYFc$ 8VZZb}կ6uͲ,$I|>bzO$( X.ØL&L)°\.cd2d fxG:(ӟL&h40@>v$a:!1߃",mT*}rr-8W\.AjŸɟ`>3a&0...!iB 3MH~XV܊qxxO" '? !rhN;ϳKX,X,b0 N3H0 v3#>TwL&FYϻH$,(,X LfNX jqww׉uTB4ah4e\`lwww( fDEH~Ίbn˲N x؉C;Yכ ``$SE^VnH$8;;91Fd 4MFk5Md2Ld2`b+D#Q{DQ,F6:l8Cd >(q@exi,}>97*C G`>+O}oh\"Nsm2$5Gݻ_fvY;+4;?șNq|χhrAxGP@$e}0pz@ y@k á٭@eFP"V+>+-vV}UUCVΩ%Y}>^x9Ob2@uhڢaWFtvd2 ۶M CjH$EX׸WܠlMUUnp! nh uS$H0 ])N-;Thއlc\b0ދ ɫX,VuRFɠX,T*NkIILtSҞn}h4@ M8C$ŰZis;$I\__SVp AQc8 $qE4CnHRz,jg2owwA+4;?i6[/eZbBY`}o٧<{.[hfDZPr6q`0`w\0}0@b%Yoe=;5 IDAThxٕm[5$}pjBE>G6Ebr(E :&slFz=r9%slr$lހ)r! }^Ǜ7o?L0ֲ,l :"B(rTzZnc0 @hB777@H$ ># Lj񉴮0 ?#(`^dLx~~UU3{OKɦOUn Gb,XVP(rPFT*KEA6m8>>h4?c4j_h4]ZE)".s N a\d2AZ)W[R4MN-(褐2 H(J0M4} l6yZ)NԐN1eYf<-nɭq||x<{_F*{}4MK˗/XId2ARׯ?h(!e}>l69P,!IhN0K"p_$(-FM.c`XCw a8F#@|>>!|PGGGPUFᛖeիWM:9LHXA] I9 ޽{7oޠlbZ1?c6AQiHVQ1;h9Zhizx 162 sҽ!h1!1/tNB< "',8==zfq%|P%Jѥt:T*ǃ`bEQPհ\.Q.;˴I > +5S%wp8acZvzT*›,;9w8&~\"!L?C, DGf?B`SMӸBe(F,x`˯:Kwq3;g2CsA.01|>* 2 :81?h0 l& {tO3nZa`4b6AfNN+QنLjlۧP(B^syXu(r{C.oa:6Η>bn[#z@ `Eǃ|>EQ0 8Ni],O}x^ݎ-Xo7&L&rW^a^#J3t:f3QyBR2 & (&H dCl<]Q,~r̋F%)DQD&x<|$I|xPVX $j~6nlbXdGA(1!" oD' 3@8B{*)2 b;έ3QT*R7.F#Aaqr9@UUaf3gF́b`#2TZRSUU^G $dy7Mڞ0y$@TBXD*GrvP;HP`3H"F" ^)A%M/bk8^c4!K^dm\.ң(n8։Bp5A8*a&?0Hp=YȩO4nr4 CR/' իW\ s@rT#,`EA^$>iBPUap4 s"TU+2qA6r>tpyy``Ǒbt: ?h tJ1^Fj6ekvjl6:5-i11MdYFP؟;02 7 NӧPU错R 锗+rt:vZЉ- @ZEXd2A*bx78tH$DfɮC4 d(lY.޽{jFQ*CNj(xXQ_~1l9A mM R:#}d2}|dr?Bvj,#cXX,\.uE(N$4*Jۅi+HRRwl?Q)~||2 /fA ὣgԹQ>!Rx2X0 J%t]Ɍa%x* hZ ^J@ "5ٶLtʠPA B7n;Whpw>98\.]!\.vKUA}~Nt]G:p8dA0ѝ4MChR $I\UF= p#AE"dԳK}V?eOmHژϵ/?{@Q^` 2$-૯ f+6%IS%A͈3Xjky>%2Q:L&/t^!50noo:TU5!NHX5~ht_)B,C^ F1P!.10}0N MdfD:|>:w7+R)F#I8H"=D'`b`jZy@uJC`i8==u!3NqwwKT*b"f>SOXT*u+7>}[7t:eY8==$Il65|ؽAlr~d2ȍ' q j؉D"xͽ xjZH$s^3b>lH$8vGUA2I=/(0C:J2Y(PAOa"m0ķ~zfA3Y.t:FEqLCח3Ӵr<un\w(R~MP(EQjL&9KYRN6 A@R)Q!zx9" T \:?E((NfmmXh:G4MC"i-T m]Ԏ8~R(v:<|Ī e Z`3 <TUefa6ՊOGAgOOX6GR(kit]h4zɛ{eo?UV2 8p8[ d¬ZP)R#"k hx̋  |qVχL&9zRv EQB( cm`0t:Ejv. r, $Iv1 ȍB̆/^@. 'a Ld p}>N_YDz AjXN$,hZBaݲ@xp~~9i kF؏F#lJ$8#I77̳e&-hjXV8??nC^g fY> D$rQO JlǃFy/BC4F54!2FPf,nw^DhrBc'h4f,[E>w* <-m~ETCJjF*өzítw~ G3eTsAy8"LBe<\$$; qzz EQ8Mi"q|tχd2RW^A$P #aq~rt[՜~N(Jx1$IB6Z(W>v;G_ǵORWjHbY0RQX?񳸹awķmj4MTUH<5-? ʋv;oŏ~#J%Hce Nc\bE D-K$ ضdqq$Wx<sz a/j  ~+ F| I& A#RALnfc|Ȥ3,Qi!*bUUm4Mcwww㼨u*TP,?6(rKٌOضo"H5x2 H$$ImPR ,C4>|vgSBuv@)3-tx^F#L&{GaQd4??wz/,s&b `wmۘL&(JV"7x$Ĩʰh4;YG=9+SF^x-kaYb]F+NmQǹZxX4MPW4nooN;ΜDtjx<\.obٰKQT9njh4`&J SIXYWԭtw~ G5Gm˼zȤ(W+k LR h6f3v3$IA4^hO&x\bQ4wH&h[ TtX7F,Xs%,\PT`d CHR,yynozBTʍ/㺿U(mx/ڀu*L&97Nr ]ٙlpqqApssb`0n ۶N eوF%lwGDQ7o '\G"$Il_o4P'h9bc.E>sD=A_ڶ  C4 $ A4M(gǃH4V )&A[,\eXhӧO:4"VgrOYyPPkFχjmw6 9B<dY:Xf3J%l6l[. F#4M?G&A(BQW†L6r̄bM^}0*& ԺAD6E(:XJfb]fÐXJO@y @Jv;n׃CLT`}D6}Clwu?, T `_jB(ԏa2Ws,xhӳt,W:V+vA $;msCA$q{A +sEQeA4vPF42c }n MHL(n!qDz, G? k~L&Hz$ ł+)5H|Vy0p,m:r9L},c_u,qǝB;QMݮ_^\<T/v&I8鴓x<лvpX,Ƌ,ekf)> @XABd_! 舡sihp^(x?mThV^o~yW>&?n AE̛fK1h-3tc0Ax9 /bbPi믿nCeBb N^JHcDz E_c2`rf?p5&\aY/KtI0R:A6M91$u(S8 p8D.X 1 H'$TUEPXr*#%IDjll6t:P$A<ex<^d]19wY駟^v!2GRD"u@(b9ZT3n\o㒔e0u82d{7ƚQVDQGH\ -Au]G*$ImYfYz؟;ɥ!GHZ9N`N(Ehxѕm5ðS0P.!I d o/WI9Y|\F:i 8Q HB>O- {DnY,,F!Sv׋dl6nܻo ?:CYjX,GDz:$}! }TN4G1NOOy!6 GBh4d2P(s * v2j^AUU`ݢZun ̰F ;HfrpB! @O)AT*M/899뿲P1 06VVcv5@W49S*P*j?(%,SǹE/Q(H$b5 IDAT!" mG9Z2Y" \u oP %8|r,\i:QEQ~ BHӸ! \E#Ŷ"WqZ-f3l69IaHAPo4 3]D?f9zJ.//Q.qww0PH+`Z"IĀ q1a4! rm' Hd俾㚻;Whpw>NӃgZ#! V={rrׯ_öm$ pIr|V gggv8>>-3HjCz}x Ys 28sB}\²,t],& (vZw+)Ji/S, Jq}6i41Amh4st]d90w7Hu:-9.*2 9fJ0 /Bj5s^H1LcG! "tAh+8qrz M[0Re h4W7?UU!2F!2 noEhHcW^Pn uHRHM*bK @ei۷$ $38r"!q+?n9r1 ɄY?Bm(bN @N(˲/+Y;?qwq?Ҳ̿,+#IY^j,FV\6t;c80Muhs Ѩxt:frN1 l$Tu(H&Fz\n{a2r^ʝgo}\rt j<$q~փ^Ǥ˲^\.qxxϱzX,p8pHNzveA@0p(9T4 IHzNZZ/*o64>=Q:&>D >a97Oe#LbAUUNK>)D"r\7ItHHtY>$7Rs="U&Ivot@33.77bL&8i8??G0^/"$0E9TUض~Ϣr9R2Aޠ%QȝD1+"81D1A :&Uib0{E1f2L+IWu(|t@(CVEQD( ՝n6'^n?AA$Hd.ŧ|>!xt]f{D1txjGGGhįu1q>e2A* 7}DY8v|/V+n f"j' 9sGℐLn 5vd2ߐe`fY_uw+4;LS`(X,Ƅpz[,%'Zmz;M=^XloݷfH~rzhQ]b<g}T >DpL;jPVEQsEGw(AsfP#?SR}W*B!>a~׃ {9uegY\޾}Vsc~*S۶2'(1Lh4h48OP㥑^|>ϭ ezH${?["d2n?88( ^tE|Op}}?!CeZD"Q9@m)2/ytOfEAq*)q>3PL0Y\Vvx_;Dv8>>7|Ñ#׋ׯ_c٠^Anh4Y! V*vPjBc X|UU5+ @UU>N( zy\IsRA<7|u}@||?~̯'B_.c~\v,szF  9/vĈCx"4l+۶jTi*2I{Mx)2a J'jagId_~y-$8|`|x_ݎFzX@߇iǐen888=:X,b^cۡZ\.c:bZ!Nc3P,a1XF'PO)#躎r\.pcgWU+NNNl~rw_ǵ.WaQٶT*8HjbbEA"e|4h,(Z-^~?${T s*GT~zP#XjL~K$:#sAuNx̚z1 ؓ!{>jafX,`Y;r- pP1=9ltD?#F\}iY6r$Q5pe:b8A@DaR V]9O^bq \T*qɓ̠ʯ~+t:ATK9nlF&F5%޽{@Bz$f~$~jP7'f.bjaF'''P'a;F^ϟ_}CyLSv̈!v(ՖiD"(J0MQr%_޼ycil r*1"b"AN0V3i/;k" hTm|~Y;ŸB;QOz: \QT޷FQuj5(Txr< =z\.o>ʲ,b[%Ib|<|. R-kNllW^FvZL=zCp{/~>i`pSfۿ|Oh8b1} ^iaDB=&¿olǧhd!K-l5d(r;AX,NSnjPU]qxx`x.F4MEnt!!e:: D\V[]NOOaL&I^Z\.Y<E5Q*8 SjZ"1t]dzgx/JXB71]4MnG!j4MLSDl[,ANj/Al[n |UxzF:fQDHZu]ׯQհ^a$eYfv?"dY5F. 6 ֽ r*HI;DǏ8Q.n*!0P T#'z>00pLX,X.JvP[ /^fIx>$w^t,qǝf\w׫d2qa&j'hmht ^ض^ǧa[,|KvߏJµuGGGPU ш['0 )"ualְl @:˯^b6#C$ݟ{~jyֆd"sgBkKbڧR)b1+P}0mEE4Lr<~Vlc&[4*G<5t^N2/mbmI.vSP{iz,6wjpL:$Ah4,4 @F 4OEB$( -%J4(pȶx5߶AՊkE)c&ЉO|j899l6$IlO'Ϧib۷.xtJ(eaXK[DDNSW32*!"2@Bo޼Ea%:7|P|-jLuϞ=hPPhv GU5<0/H Nc<cX|,?)',LP&Ly p Keb1ʖ{$t:E:F$ϙs||H$J(J-2^7P]'(w: j0- ~cLpȯ8(T{I$n̼Ȉ)RG jx4` <x0UU95l˂31UU̦3,4 |}V;KH$vJ~n;Q+4;~Y( ޽{'H͆OUUE"34EARaC8楗0A~;k8~?jgw HEIY N fl9ec8FCf5QYr %x</^d2舁BF,VacR)}$)vbC*jS%5,躎 u>6M}ѣG @BK4YآNFx< ^h6|I  Ya& l{':> x^y$I4Mm($Ib'ew3hfE#2MV}ض ˱v;M{Nuwww(8>>F-  .uL7/}DةTŘT*ş}0d<(ʘᏅB^K(8}D"=TRD:f , V!(1^/* E3w_|f~k'p r{!^~"*}' Cg:ξ{/BC*n5rL$!_kAXs>"kɹl@Bjb!mIУPJ4vc$ *S)G2 TթPr,b6! !^t" 4ȕBk[TؙvQ*na4Ş_8,N}#Hl6j"+P(l24MF#~a_ BHRl-N&$qzfp8bH`Z->#"\.Cel6 X,tw!~YfbЂH˓ x ~_ZA׋z;t]nXVm 0`ltلܪX,C(u]b`c0,lv0Ϲďr Agqs Ua&7Vl6t:E΃clR7]g@i019/$`3Hsbb^31b*TUvE2eY2:HG%!D*bSH5Οap(P:%J姨1gY.,_75h(G$:3l,BقT*ϩmA$AP Cnx?1k"4~|5kRǃ^\VH$R1ǃ|#l6 EEvP]eT{}, T j~-mnsRp 1$6DQ߳l8 *EI˲fixw άzVъpȢpcB:Ǐ@59,ZdIL9agnT*LN&l۷` |>@,CP`wZ.P(nOtjk ,ӂ/y q" a<ZٱfeCDQPQ(0Qs96(r,c_IE߫.} WٶzM?z[g;| {f< Mil{ 5m= ;+Kzd2LF lGx< 3MDX+<^/!dYXp=; 8'rʲ Ih4Lj60~t>o߾E"aMӸO]EA(BR(,0x<˒sjX={q b,s$h: IDATwqO}$Rԩ*U\wJI0 @@rcoѹ_썍J:=ۮJ*NII}A׫{3%Q?yIhpuhP˿ x<-Y).#"L 'oQ,5Ye f(B "z* $,%G%!,V* O۶Y!5|Gir葚*NUv]f0$ l^^.b:^UUΔ$ ш7@6eYG1\ *4Mc9A(@QțɼM`?~̎"m zbUn62lf.$IfOPG"̿m0g3vB'\Pwww4?`?ٲ,|L&ٜ ۶i1O.//?4qv9 ߒ$0jBrV` $a~OP߆M$bgCTgM4Hh&h>ToqNOXjcg<`0@TgOkp\u^%2 4MT*qdO>a(-};2_י;>Mm*G|h:"`00ȃsQlצZR߯~+vml[<Ƕmc00%|lg2** |xi1s]By b{{{h4H$$(R3 L&j8.v)vA",ˁ S@vp.WUa6dp2N9r*j< nQ9\.QTvy%K> lłGk(n!)uL^Q {(BBev4}{ B^~ߎb8==9a418 R_sp@T.T*ٹ!yXjCq|r qNA8zǼSric[rz-p:X.e9̺Pà X,l'|>gQ"qnc^3ۣhp^:N&UVUj8{GA^Պi\8/ӧ|MPVfH:RppR=,K^-" Cpl6q(y btM#3h BP?z 0ppp~~rkPKmon 4yb80 D-Gb1 :d]b g^cp{{bFAMg[V\lj#F#^Q-`0@Ry3,h(E$$<Q( nPt)q#% (xAt)KN.pQ CX,2ozKR:=MlFb$IEEu=_T0ϑL&l6dIp$;j~Y,w]brXO@h4qt:PrhƱ-^EuD鉄h棛jkAqۡn'Tsض*ZRBxnoo.xEsqqC\SEEi1-[,|MVll6 ]׃NjPH+ J|t_(OlԀe-L&0M?ENd2a׀$IO 0\.+ ?8BS :\.'prrB.w!.>SzŘb%~0@\!%p;,Kl-₀!^}2";σ! "tCGDksfb6Cf/ǠU*xwqBgBX@|O?4m4#TUȲZfz+-۶a&d9m;zEATUEP% +u$,R+A28^zmov ?LH%s ?E0Z9zRTpzzSc~t?*%~w͖&c`2( 9v* h4_Z,:;NݵZ?ۨ2h/?M4|ףql6|:޼yv ˶x?̈O&O<п\.ٺ|{{l9|Z@,.fYYK{ *JP8F|祊xL%*%iY[VL&lw&4?6SF[$CvB^eYHRhh64!2W/I6.l[OR<7fkdYF#cl[,v;ضZFB7|Ì uaA|7lihZ #nc^<7a#TIH՟+g x3vnL 7r/O>zTje߲,,,+W&0#9] "nooX0"R$XhM0v8X8b7>D,K~ϋ-/^@<*4Mcŝr2?_jJ-?>G=> \Z"4Vd:PQHVx4BVǏ5Aђ>dV+ \3 p9%IJ%jv͑BGe:Cn*QQP(dfeTgM4Hh&h>7DBzhZ<R nHSwgCQ|hz<@K$-WTGpImy9 BmېS2 vH'of$RmjZ8QT`Y4/ZJH:Oz /_ׯA jIN9idYӧOI՘do6)JAef3$ Z-i n$) c 0D\SVAUUٓHv8V+۷V=C h]E^[4MOSLSpR;tXPǏQ,e#IJ9,z-Z Nc4ԸpwwI0Nd2ɧ$úGX.,P: b7D -KdY@:fZ'F_B A04XtNq]p8f]RI82 pӹFZ#dYfA@$hzϫWl6q}}Όm|>jNd2ɭ"ߛt^]]qUMD-|OvDh8<<z^C4E~ZH$Ȅ~@<=R H1Nv 9<sT1'v* C7 & t]ZT*ŭ >,ˈnv|>d2 Zid2ɜn+$Kċ'03D<BnjEuL$^9S)Ȥ(X9f ENa1kB+k 珏OtxR77k4^`--n# Rk3X i,m^߯4%BH/dob $#qDhYr,cy4Mvl6or={r"tLSK"/TZ l|V_nmJl+Xd]EE7*q,y:էjjh6(J؅Nx̮ A4:.KTU| BЦۡV͛G="3C8665 CeB!UNQUs9!2 EA\-"K& !'I\._6FByvn4ͨ2h(&&zR)e7c|6`TUE2 , f34M>M?>RbD q^8{~' TU5:"rZ& / tKD"j.PRal6ێb%<"u]<$}`cJrɧÞ0 V+t]$I(YQNK`0NbdZ)jp]mT7Ow]LVPx<`L;|>&v]:8vďVAe|gHx%+ ":nsRs7J.vBQ$cuv멪*>$Ii}L`]8TT d.re**t|}ŐNZ1N!28i `t:e!OlåF= S)A=laT*.c~FVR(I2B\ǏAQ)&).j{ W`0@,cam>M'n(Fu$P(plhp">c@V x@B(IlNVrURU777jjh8K`Og1eR .X,2ř~D"l6 MYd7DuDG3M4|cM+}ϲ,,FQJ+r CD+p`…#`˲؂MQ 88|w_=u. ,B;L IZb4MEbo,J Bbvm YNfqttđfl6 ۶L&Q.I$ӧ4 d2J|>x6q̅`xA@l6u<C ]|ww! pdw])YYl6v(H $BO~vvƱn MXrf^B1_ $ LS 84M|E1MӸ~x"B +:F#b1fCP}^RC!c2X*q$NsƲ,{0 d2;hў(Jy@1qإL&!2p˗/q{{x\"ˡT*2^bnfwޡj1+BSOvBuнlcv$ c61'N5A A<5Zq(%oX, l6H+ >3Dhh4.6l`ǧ_ Vrϑ5rl[hB|>g=lFexf>gFQ(xrn8jx_lvn,6ER)u躎L&_FuDG5M4|i_;bYzu]G:f5¸nNc:fx,pR'(b:h4⊾}St<,8M=Xe&>e]*ԏ (bF΁X,JpyxQ@lZ lFvr9~nvxR4z^xz0 p꿛b>s-'+5  ܰT?iQrvt6]/IJ>4M\nCaao9ϠL&brR5J"!_=<{ N!L(BETU$`X9۾m| 9;ȭ19/u!_dPTOoD0* tH<_~%0t^cnP)rXi#p뺘L&$vu!iy>GdHߦ)8D⒐{&rkZrP(p{{ 'tTUUE lrw"4ds̎]Eq777 l6XW60CihZ,2nújʟ˶m0 S֗t:V0U ߿y!a4DCM$4DM4L&06xqį IDAT!N%׫5 FS%b^OS' ,Vs\u]`qX n :O&a4h40ymmkdH%-fSl6d2`>j:f3P(.,hlzl6v'|_e"H`^s_I1 zT:LXL"8q]~~X,b\ ŐQU<Zt Ɩc)8bfYOQT*P||nlIޥ8A+/y!3*,JI4j>ToyvvrZIaqPE$o[4MdPAE<}~,Z eqC ҵZ uPq[gk dNI|?2h&&$Y|Oe2 K \EVW+@R> Ln/kEcݡX,uNb3\,L/ȴYდ_C\Bpj$M|i+PUwww|L&NL}#af8"N#N3"+t"88b@pfVxq`fkQPHF'O@UUvZKݻwXu6ɠ@smn-2!F#oqssUUyQ?==xF< ޽{ǹ|")r躎fÌ 5Ac{Oy{OrPH"@Vcf2`^T*q%_vEXD2G&˲nDqnM0 TcHO퉽Bϝ޳,]O;Dp]7U0h4( Zҙ B&QQ*d4A.4?`uTeRܠy;!Z:AOCGGGx <σiV*4A $fa __P K ˲P.9rCϯQ3x!U(.Wj}  ޽%,&n"!hAiVzu(_u:{]۷A՞(a0 LB$uʼn J\]P,1djՊpb PX,boo шm EjhfT n6؆Tǘq||̑wd2r!,˂xH$F۶1!$LhhX,b^\Vh4mԲfi< Mò,R)nEp>c?1!ÂNV+އR*&A`=S`627 d2vb16M`x1Gcz +W..Dhxy2hW*x2FT /E6rKX<ϙd`Y& 1 ^Y8"EXH$x*r:NOOjm}`|>)τ2hh'& 5uC m'gKq@ D$ ŜXsdw></TvtthJhk^8=z׆3o[h2L&qwwB'N(\Na&[)^bO f,n۷\YJbK Ai$ iA!X ;58EjqX,( 積Cef3j5nǠ:EMӠ:4Ml68;;x"+\h6ڌk۸`6zجPUg Yw 2Ix, 888`K`0Fri/..N(J,|mCV΁f3nsrl6Lt:/_*`&(ha'`8r(d2b*O$ UUٚ7 |r9ߣ<4MEQ0LJ7L&1ǭb>}аYl6q`[Be4MBzzA\X9c޼y|ZO9sj ;yZ>=Zƭ%lJ7j`4XrIs]jznkDɡ "nooYd!?e)C(PUϞ=(x2sm:ډh5a޴ZO5|vxxf l8ƔOVvvB( Cj Pl6>4Mwp٠``m, Ȳ%I?:AT*X.̓ GF6E=3B8lmrc$# tX$~J%\fr;sZ-C$,x52߈O X,ƀOZ  ;TJpxx ɄkGuj|=(AՎ$&r9;MSą8 T W,!HTzC%mFՂ(ob#QUZ9PVyz=y, B$_fzLJ"h!U^ҩl6CXd$ THjS1B70g^X, ¹$Imd`r6C DtbȕR~BuF-fx1[%6 Z>=& v(ˡ#׿MTgM4| Dn>_g2o޼aXTb@:9% C&뺎bYrQX.匸m $ 8<σ,˼HrĞ^CUUn, 3zkisIrlONm\brx›嘡@u +<(WOBV-Y$ Qj73 *z  yK Hlg:BNA=z %=Z-,T*\__3|0>01$/$B/8BT~c EQN1 IE.o3bݲ}fjq躎tʖr qxA&a$B%R)'Ir p %gE*£GL&T*,PS`01;X1?~b??:aXh4:& , ޸0 PJ wTIpuQT( &Re'O-&c 8ftxx- peY|mEQdN1bpx BfϛͽvA&AJnn6X Iy֡lZQ0RE.!xoq; R䂣zY7}׿ykM4Dj"!hŅ),~bTB6Aa]ORRp8ng9"^~ *i"-鄟bl6t:n|>ϧ`tIٲ,cZPwnnnzkvx> v˧ݦiœP(Yi \ҡX,@ט*BnmCUUdY<~@Ruxa6NirC :GUrMrrɎZ"a# PU!$ZR)~19&*NNNpwwt\.5\?ϡ*{iؒN):@ׄ[j5gTjCH$+:/J(Hd2ɋgXdbt:1E Χ'(\5IbNG\i,"t:L 1HĐ/^+xѐ$7#X, 8|~P (J`6 1 ~0?bZ'( "2 ^|Ft]d2 `@1ϖdX,ήX,u2-A orKBTP@ A\EQ1& `SlX,=LS849{Z'6"sl6a'GJ+ g|YrF#xb}W(Fb'n5me7&h>h9|=MAMgRfo QL&( 1= n#!\ -+no:K؎T*'J`t]g>$SCET1["1NC׍d2!|>kq$DE<9{s:mYPdw>Ԭ bx|vnXaPn˲xa@MӸe#H`^P,G<%Ҿv[xEA\JRe?!)$b1 G!I"b1X,\.a^vŀj ۶Q*P(0WV?X}Gۅh6]޶aNH%( a0@ ,# @ky<sdEsL&X,&hDm;8]/( 3 xSVWCecLSZ2D/ 햅nx<=L&1G)~{fYsCJNbC܈:$5 `<b>^70A |k~j.cjv)3S2Ҋd""ͅA!$Q"dC0Tw6 %mƦCv rg" a4C DE؎YQ30 qPr9nZ&EQ=PznYV/^0πX ,c4(C@hAx޾}g<GBpxxfkd'y, !}[m3lhgdzʍ,&DBC4D4Wx>-l; "?~wAQ,K [& ^UUl\\ ~dxr-$u(Q(:V1Wqyo C4V֒OcX,b0} Cd,C1O|u19'NQꪟfx97Љ" ŢjMPV߯OL"v-T^X.XYzRcz-& ^zuq||Jxiܬ2S*>B`P@2ϱ[P)%_]]"(b<u]Lê]R,:׽ Qe4DHh&h~s}uAcXNldd2l6l/ , V+ -6ҙ `(̽|3E}>-J4 ,^@2_=ĵRy6k@m~puuŧ٥R $x^so vq!]_V1<DV }*D\.1 C:88|gXb.eY9AT:4 7 VvӧH$nwzn[4e9p&>$I7֜L~,e,skWCZ$L&GG,Z'}dYg:F:h4e}%LSd23kzd2N۶H$i 5fe412 TYll; @ Tc.[7 ,t:eQg"PVm]Kt?{ ٰZ}in@)N!=${&drQ-.Ev*sg_ rQ(>.//J> /_hbb((r 1bpyyJ<Nv;| -JAj6CH5 JA,'Y,2hL$4DM4?⋟Eᜀstb~qql6Nan> 2©_]]|<tLI_ I_J$({{'$zDoS|`<E~^#x!mD7M+gŋrnC C>a~) 1pQ^P(@!͹^PV,x<O캛>N=CV}(f<| ( !qgl8գ IDAT#RC-(O>8::e;2(C'R=aZ)!nnn S49)rfZ!2AxL&'҉dXquVKZ-4 lܦN^quuů!}Pi8|?Z. oyBM Ca( $nʼn`4q\,L29bQ(p}yaooJV ix6Xb>q&*Ch}wwwLea`0Ȏ[90 M'0៲rD"vH v;DiΰٸE |@6D7XZ l\dsrJEQP,a٠hp;e2t4fb]1x-Qdbr6 $^zx<UU9/Hm;pf0 # 8D$1t]G<G\ƻwJWH!r; |gуN)&TU,GDzNB:2̄w!Yd2d2,BxFA`ZE\ӧe}zn!yj L&*}:} ˡmХDq|>ύ0t$pHJ5 0Mmc^330/_d*!HpNK~!Nh!ʱl%F# )-G$IׯP*0 `6֛ &~ŰZ ETUU,lϫǏ3|u8B$vQ{Ȳׯ,&DBC4D_V?꺎f:-x|TObV8j0 ;OT*r u1.f;:C\Jt^(ڎp E)lf'tjN EQDd$x.N=NX8s Bz )& i9Yט( (|/" EUUQ%i`ټD]p8_|>N\.!pxv; F;nDQdQ  \#sD??W_AEn֠1.b]oot{w"4K0jS>b CMK>#HZb4zMqk"ZrȲlAfYhjDf|0L02L0۷o(?(s߷N5Ju뢐snXPUf%T*qL> $IhѰ.mMUUo6SF.mvS)\ϟ?vm 5 xc,A- u]ᓐ@K?-$2l[EL&x'WAd2IA (h4xz,@bj5Wzfk:AIJ YfhZ.Vk SS4b1P(q)Log҉:LaA"m\I,X.8[nPUE{<}cI!i4 ߇xC5}R(03M|',HaTI<|rBr2jd|$"5Ms ,У:N:1W:0 yKmɓ', ᐝRPhÁ#!ё |P J1v|X,h4~"t:ǏفCb[Ȳo2ąp8lw!$c݉p޽K۶kvbHDZe |Dzrx<Y;yko4^i$.?g= tt:Gcò,öm~OSV+HB\cI jg9~4DQĻw`"3Ij?$Raˍ,˼@^9EA~G\f"}oj ܏P%۲,r{X}.cgX!!vi(D6eYhX, LWU :JJǼcb~M'9>,TUEnÓǏ+xT hTR n#i"X&qA1%1`٠0 !TU?ҽ\;e:6/$:@nbi"N#H $tbG0̳X,"#,|japhv v$zf rA4ϵ3މАH$.EQ 躎tRV\.jżMӰlV_QIxP UTRs JPL4Ml[V+79<99~G$U'wwAe0;7L0O{J%^P1L2D'ł-˂m|HVg:1Dԍjaؒɤw]owFq{.Q2rnnnnNM#h$)~n]@ QCCҏ#;8ٳgp `s餟N#*ztZ H$p{{GʿSv=|6 &fa@uz==/jU$LdWwN) "JGBBfٱA b\e:># Bx%/X }=&پN>b,TU$IF#sTU6:B,JP߳s! ^CE^ٳgd2d2 H&x Ͳ#\.RBMŸjm< lI "9w&EvhՉL&PU$i8 $a8rcnC&hnCeP&A )w!L6 0( :-& Ab/cd4a\RHN4M[˲>-˼`/ TU> 'vAVt:|>g >tp8| Y#eoNW:õTkH-(ԛ,]"x!',|Eă|>GdRfP\!$H/ĈE1JիM$zNi,^DQDÇ!Ce=0Ͱ\.:Ea _ Qljc8b>l܉P.'I-HBHPmcg>g19ˡRxP[Kbr4a,0߸B8ȒmPgL0N 4L07uռSl6rd2'Xx5q^* g\d2A.ûkXUUzݸi|~Ot+ !RKEi4_unޭ34 <|Yׅ~.[ N8t]$I(HӘN`Z> .IKeY쀠l$I8??HV|>Ӓdfh4H,lbA9'a4d2,~{vxrs(~-d2k@|H&XV Yt]Lbs&49~~p(1P"$tBMFjbH,,ans[!!?zxLEX,sv[p #^777HR,uoQ*4~.AeӃzyyr&V+$!B4...H@PJ&( ">۷PUǞZNQT "& & 7;ض $|,˘X,$ Ji:v O#2Wn/qDQ4M#j5iSuLSE]F(ʝ[&z];(J< P. AG=b<V FТQnsXxp8t:evL&hDUPgL0N 4L07ɤfBtO[߿h4~X,d2k^`6,w;DQ$I8vmw+n`T*ծ ^Ío}y%^n6eI0Lt:A`9 &I^ojnh6nBx9R)N!JP(Z9Ӊm|I׼l\. AY .Xl8 (D"b|M0P*JIj0TMӸNS)B|nCTU}eu'''L&uBI&eUY`Y/msڮV+y9Dmc^AqbCFb?faѣGpPuٲNKd2A*""wl۶x 惐PAM"~)2-55LZptt^0J/sLM/6d0ϱ]+Lưm 777H$f@%V w!V2 !މ(F0M͆1rEc\b87Q.a6J>}ku:Ld UB!^җ%p8`X`8݉АNg.c1Gp4V!skr(1tDQ |B! >׏ڶ& F( i4V2`@h&`'f!I__Q&>|aI-"_muF׋FRTkz[]EUA0N^[N]\O?v5 l[ RQdi }iӲFx~c CkO7I,s\_c|iB4H Ʉ}yf3\.?hΠŔZ! n?|B჊MbnȲ h#8Љ4$H(doL>A),B6_|-TL&Rt:\Yl6#F˲@~wł35t]s |,3hA~r!׿feY D"ć@ur,RF _Qϱ/-|xhH z\xD"DQW+>юbt CwPLLWBNjV!G.Ch4bk8L 2G(Ra&?Fr!N3\b ɄD" H$/6nw"4<rZf2 {ݻwj4vbI! xW;˗pV41C`0@*Bz?_.&L0N 4L0FfT4TUtM6:&0Uԑ=Nz@E>\vPl6?b,\$ >Ivpmc0|y0d:M9hUUa67 TU~'*1fzNr|>o"/W_}X,b|rjp8MsufH$]JPG~] I +}8::B8FTvOKMq4Rxt:L5~KZ^ I\ ~[1 i/ЮB֦ir=&-*!ݣ(@'''( ! {=߿F\1r p\.sAut: 4akܰ8)R4C,-;ʲjnNb9,H$d2HRܮ.(96d9̭iZ$~Sr [! {5@- ֡ן(f#JR܆C1Nr&9**\EVkApEj9Fɝ '}:,cl|m6jd2ɎFT*IF>snd2(,s^hZa<{1 ?3j:` w~!` EQ!?N^' W!>|s$H(L'keYH$l&+raR{Xp8D-^ |gH]:Φf&"X x,^# "r>u]\]]Aݝ]$Xm0 aYF:楂ZD)jp8>xԜR|l 9L&xfld\.pY9ALN6h4ʹnl6V+TUuqrrL&njWl6|{uuv˂/ Ȳ n#sDZ3ot:|*LkZNja>#ϣjyΰ Tc:ޢH$vH$aQI)ƣ( 9A XR)NbP܆b@C_$t:a0b2pc7Xf3|u IDAT-J@-8::ޝ 1=v9j" J$T* $)˱DM'~{nu]b>DVm\J¡EgOMsYL0 L0̘iW[?ET\.BY}9RX g`!2P(09 2Xeٲ֍`]\awy8jt|8qvp˲!o޼&K'(2t~4# 0Ϲ~T4MdZGkY(Hbzl6CP|<V+L&?l)Ȯ}=\]]P(@UU^ԝ@s#~6<=4G dXvzN)A'Og=x1 h66"#N>zp8M J!E8 \wPH$zDFj'i>0 k9!AwcD5 T K߽u$>xEbcPdO)dEZR)cF9T HaBvtjN -Ђ `۱*a7 F !pĐ>lD4 n,s!,˲?}d3i T%.*2t=DRA0*C"E.B!4M(rSHd2xR"Q N-aTt]w}ǂ)smfq4>3)6M,@QEQ'T*a>FxRa)1QVM\.f GFPWURc U rK[&ᶉdϧh8P 5PI$Br~qP,5iZ$|W,|ja l8vY*rȲkD"Ef3gDLz(l`\1cٰ{$H*7p* fF 2>;285MӐ`Y:Wa4ar's"ZY9xggܞCq"dYtY=<ѕ([gPgL0O 4L0 3 LgWWokb[7My/L0 ض̀P(0 I$1L:-3L&AT*a\Bu8X,vFh+[;8.,'%Ib{u ~"~׃,\GHՃݎ)YÉ@%)Al6C\tb1vLT*QIB 'tzNNNNp}}͢jbWE.jb&e=n޲"pm`6nc}8FlEVQ,l7~}Ƣ $S`4NcL*6 fcet:z, : XVN$RFW:%b )D\.yj1Kcg2vNt:a|Omtv;m޽{,:;;ix4Mp8D6eGUU,P!qe/Vgcs#w2Md%\777T*jOL:d2ATbxS$  0e Uv;R-pFMo%yS(Ws)2 Àw9X,ǡif3dY8#v/6٬|0L0e!` _Ve)Cϳ,,½{C4M(„~˲nnfk>bbK@ʭk ;t:_5X.(TUUcHN'^~O=\E$a5g"K>mx Z0:M x0qrrǷh0 2/[@Z@hXd` q<-X,(T%,E" ?Æ"{`0Xt:iu1/H0ԒtKQDXxen6Sh)0xlRq( NNN~#ϳ򾓇GrcvPhZ4{D]1L&Y#}_zhp/_d҇=.9@$`8ӧt:(Dznh4.Sn6y{$KF,TUE.ӧOS\]]yu~D"T*FgR9* ) v~X,9>& 9P$xnq4RKIjՊ7J#*" }4Md:el6@zP咛Dvssr,KbuL0d!` 0bJϻݮzrr]׽>؍ ݎ!s.i:$j~7MS@v&:Q_nml£L&۶َn&* ,%ƩraX@u^ }3Q 0>SSW/(q[@&E~  lsa#L"9xB6p).5MCC4E$B$^$`D֏F줠G- phI6@Ļp8d xI_CUUf1YtB!/>!I͑sc& >UU4It]G:F(O~cn( pa%'ˋ/va8DL DbVBveW(bBM9jq8>`_P4MWKOpȭ5BDpqqsOjYqttjB2t:G}rMxDۅa\D"xka>C49. y˨ii?ٶ߯lZLD<(Ih,$d$%NcdGQ=6i%@ $4M{a^qE90l/L|A x&=fYgSp܄,F@<>WUIۢn\.u]B* Lo[l8 0Op]M蔘#LSE#qH4MC:FcA4y1p8Pnvl" LjkZj"N`X,`Y`mO> TUZqY2^Q.1NX,x/J|˲SR}zvKd2a8s^#Ja>B4(P(`6yGc CEQD*(^gNd2X.( X.>ض\ml6PFNNN^bqZ1 t]G.C.x2Vn{Qe>Kj1{(H ,xWɦiqE}N/]FzmZfYPt:|P̊\O(`:hD! nɄшER_PlK4:` &f!` 1uz08 *xn_ncX`&G"o E; {9 RVyVXA`kH0Bm "Lbfx;jY[sNOO!2]Ce!NGg E<fɄoHmTE8MRuL0 ` &ǘ^~v8j`E su,Pha0/`Xp쟢V+^=zsqdYFbb F_fS[,vFNr*x1lYl14$& "$2W0})Ql\.ʼ\.ضR't L&= 6\=JR$^ZNUUE؇OJT*ŧ|vwnnnN>pq$I)s4{p &BYXׯ(CuqZ.4)4Mljhx߹A񍣣#n_@ҶmdY CL&<|h4lX|l&rv(JKiqss %M+S0: 8t]*8& 3PTUO@g3 7н*;\1L0aNOOq.jQ6^# ŋXט ~l#ipeY$ o߾*W4* ^qT\f\eaѣG \.p{G,_jVRzp]ᐝ58sۼE4eKpab9pTKlX/:` &a!` 9$w]eT?vÐaehٹ:~W ̽xd9 [V8==EzF@։D2~yjx:' :v[)xx΀`tr w]? ae)9-/ 8`,؅i D$+T*h4M, f$PEf Fhep](" Ԅ:=*\v{$IAr# nBjh4 3f4V!$HRV|J.Ibdc $*n_ncիW$ b/-X#@??777(>?Ƌ(\!{4eP( V iip@XdN11ip to2{DŽbDh,3D)ZuN<zllHi2c`^8fl79?&CUU:ï'#T*oH$HH`0wO~xL5]׹-$\'H)8>>"Z=";Hd,o -p IDAT &` 柝@h&`wimpXT*ih4⊼kΟ 0BQZ-u.&(,{i jT*˲qsS.s"Tvttd2\Z>ZOBS,0MӳlGTd;NRhZL&rW{Iׄ|ޣuv j[apip[}l[9%yFb\:V.V6 A_5Hp9\<3 /J8i=AgRoooQVva"BUN&hY[v;x̮Ky$A4o"^X,b:G*~ Fs>|+4MJ% ϣ?E[ b0 ϳKqvlcl6,x8CPW"ǃ)7{jmAKb?a1nj9 b1,K$ YQVY"Q\@R( ap Ed2D"#q1EA2z|>(\Z.9B%Iҝ9R~ۋx,JᐝR\bBN=VqߗqDp8@$l_⫠2` _@h&`L6b1r h4ʶi]tra^qs^VȧbqpttND"A'm~nZ'j`0}/f ˀύjj1KR%1_Ǔ1X 8p=e{tzNל,ap\%ˡLz_=X,Nw18$\.38lj f{yH$`4V+@_;OJ{&FSucP`6E "7V+Dd% I t I1;-EA׃i$ 1^zblt: 4bߒ$a^s\z3D6ETίteŅp]t 'v*6 WMjVRĜ4@Lu\.Q*1A%k*C}Ų,dYkf=HVA؉ ~SaR)OdAp8n:rd"۷8??䄫L7 Z7ʤi T*,KBulYD2eFpQNF1LPL&n?hq. `7ms;K(B,c ̬׻SuL0BC0`LӴ?~d łOœ$:ݻor^ a |łO%I:uI$U4g_}%h4i:5N۶L^jx<3."2HvfǏca4ɓ'h6lF ӌFPUR vOli(L&!"T xsVtOX,~ϙp۶Ł쪪q;Ib[6ݻ\p8̍tM.~9 {at( *?tʜ۶߭@\.rb,rD˲i4Mc96B!ciuxWWW4+Z?nFAD"T*78>>@ ~mc>= s0V4mq_P5M xٌ*b(8 B!m8B)w]}EI|z UUl6Y̠^?DEz=hѨHv\(u?L0Lda*Ht`~ۉi8>>f 9#+sݝ R@n?`8u]v;CQq3IEBDBzF8׭V? &`7L 4L0l"0$IpY߽{h4l6xdPEO"IK(X,lk|˻xPr^רƑA;1Jv.$i| 1& NNNB/NZ1 i/WɄ#e!$.{Ӊ2{n dYD"ϟqL#;lx(zH$|>h4bZ>3A@Ǐ3(mh4ʹt[E- C%ABAbKEؓf6=zx<0Ps)8,B,C$H0|#$ uhϞ=cKapd4!LbٰՕn8AD"`waDdY*4i^dr;E}{K:~PA4e'FVO=|el"۶zlFۅt޼AUp8\#x f^A$zW+ X8Q)9ZvmO.曠2` 0L0a7{DP(NNFKU)dG>G*b t:bpcqZ20$ ) !c XrG2DZp8d{jbу*-Hإ@T'~ӟb8r}t:x4  l%g0`dY|Q$2k\˲8LzlY.,P c_"|BphA "2 6NOOJH{8SmX0E$ F#4M~$12A{G A2wf2Zzo,yl2ds|30}aW783! VoUU0\0nl(Ve&#L*:7,Ks #w!NHR|zi1*[Ir||>V Jtݶ5MC" G(z%Y#Ja0sA$evYfaBlm#)R "Ekh\]4h<^C47rծ"qBbk:>| ǐermܢ(IrzN{MT*e<`6c: D7)ЀdX,k*d2w-HH Z=GCvEZ@в,dY.^/H,hr unճ GaXw᥄(zHj''XfǘHRf"xw]^~ЩfXdau+#:L>XN޽j0ǘLܽ{{{{D. \Xtd8mԦP,bgˍ4s=eˡ\.#El[TU8??t:+KYm899$I899vA}aq,sHhp{=777$ b#P݁i6'LPTP,w\lMv$  `lF|wz,l6|zJRa-!9H(X.rNbLd@,zN!25>~ԠB@QQqxxȧ X,r}'O#5P'?E. 'v`xkb6Md뵇oܰZ^^c^c8"WWWZY.888\/BFyYF)ҿSѢ!RH~ַ> d^@yLy\f21 ` rhIAB6 /'q}?mj60O&ضMӠ( l溾j4xVU~R0b4pFYR=|WH$<@, >Imu)8Y1J 5?M @kbRQ=bUU`.XC!'Hr<@pj1GYbt:PU\t: I8 o10dt?#L"TULj xW"aooo89S\.dr I!"/hA+W*Yu ;] * OEawK\Fg@*v ]y:.CiC˯/2uljܠAZò,r9j8::˗/sGˮBxZpssF/^0 HP(//đd0gRUh.N[Y4?#w yWUDq4G#M#'?A>G<G>jQIf،,#Eߩh)RH=}ysyy <,b[~XÝ;sm31 hP'*~^fCd/?Ӵ̩iM `0-<3ؑjNG9{8I|"@LZ<Ԙ2cZa<3ĔN]C 3#ˣj[ k`?躎J?xH$8:D*%IspeY<S`l6lk' Zvyv۸N&㶦eKd2ض~Fu]eX (}z{(lU qeXÖi"lVIt NX@SO;N'WW<FCQ}vu5QC:wC SN-67NipB'd{nH&0MezNC.9dYx< 328R4iv9Kl(FX,Be|5McjB\}l؈cPx|WIk( }¥\.Vݻ8;;dYF*a(Jl~61 Jr<.r<j4ex^ǃzRhj?fL%tYVF!W EiC$}Ah}./\ŸFT*zkaH:'7d"T4 m+䒠ZKJ%dYA08l6 2R)Q,'WirV`BG  JK @: ?\V0 1TzqqrZrɐRq)$s {S IDATSsFH&(8::B^zNTU:ZQ..AtOiTg)RHAEH"E ^Z &V Ev # 4L~ٌT Dryw<,>*K>x|>4 X#FAӁ(ܤAi"xgUi&|{.l65dY{H$`vh Ȇaݲ,^|T*mxm۸{.-  G}T*ˢX,X,JUuEHYvh eO5!3ϟ1۶sX.#xؚat& /F"?.3*J,d2Fh _4A rL&",Ja4a^s(q=z={\.o^s l}3\!8<T8Zb1 @0_`;? jsvwc4M 3j7T B8Fh6wKXހ:.  rcvk "6T<.K~=m AUUk^^ aۃ 2ck}H&8::$899l֖ XRUmJY apΟms쉞%Zda2K!4M-¥TXwI_ոjUud2LS}{.,?W^qLSl[svM (\ )?}=Q(>aX4MEV+yj5V+ض f/#JB4 T*\__),b9']奮H(AKnmPL?XdEX izx<R$bEBYV ]ǨVx6fYܻw[J>} IL㏙k!IF)VUdBc,Kt:[Y4|t^jcMӰlp]t:j=vP Rh}v˧QeH" "E HQԟ ;88HN&Z-ܽ{O>ET˗/?橢 u] CȲ+NF#۶uk{{XLSRiR`6lݻ(vx<0dy>!Ir6`ݢRH$0N8ϙ Aùm(Jf.L&'qvv|>[PI8HB- ` a@5Vu:4MAC( 777\8Nk^|  TY.CzrT* ~}}|α:-!2V+xCA]>dYiZv:2RH~MEH"Ed#IX@t:EP@R*`YlFd2ʣ#K(J`0 .{ۉNg'1-4s$IBTihZP777Yh4d2hZ=QR #.聮F4=$eT*5-={NxQBnb~$%MV4vhp|(Vٳgh4( }8/KR;(a5:|mX.(˘f\*;mDM&jggg\Jlt:v \M&[Y4өӃ!1/J(>dY.//>/ghqGլR-~ç>,#E7h)RHߐݻ#۶777vlzpz/~79kr2GC*>t:'V bZa2P(p^$hZ@  @6EX[~JH4YSG@M&nMI1P[erB_[0ϡiG=ZE Nl"Wɲ̀?ZJpFʌw]@daXC*:KEQH>Iҿn\.yn- \{."Rٳ72LX ,RfPT ri!{1",*S0ELF +T-Iאf10 ةB'a ^!q8H!g=e8"2o0> A %H`os[*8:*1 ߇;'F09NAn0PTe,J%F0~Q|wԴpcܽ{c D|fqA="4ޭ,$)~:/ P)=Vģeit:̦%ūKUO~Tg)RHEH"Euttd8Rl[)i)˺coo_|>sή۶ZEQi's7^l6z l6HU]xn],EO ZHM6tri^G^|>C]1L`&L&8Ll;lb *d2  TEEP2[ ɠnc>3dRA@D-Aj ޽x?+NkDePȣbZQq{B"|>C\:LN1NH ՀzgĘF#@S۶y? 줡Ły(X.L& (UUt]u*7"/}!*{FӴ7EQf11L{qzέhOSt\.c#|^apă@t:̋y(?~6#RH~-"E˗//߿ ش%I(a:ܩNΝ;g8~)b:`DT*SI#& 4M|>,?0 Z݀c!YɅ@udA;瀈fnX,ka@Q l6.,5\X-A6d1 "St0uᙹ\,6eC6eP_9b1^^$0:X|%WX,ru]HY1N:h`Z!J!s np~~xF?l~i(e' leKjX,|үiLӄit·-|: (;&hnJ%8!Sc܂2,!jQA\ zlAoT*a>L<ʂLb[h/_VV4Z-LS,KhjƠLt܊G/dEZANbocr4MvE&aȝ;wgӟeyWFcNNNxF-)J{+~|(J˗FDbxaY!^F86;JV ABun"EEѢ!RHaS^E\^^2ßdy¶m|<0@t5ܿqvׅX.;{?[o1G)A@f*s+@ή2ݟ3i "WfY ,KdY4M h3˲ uaYir&#-.fy37DdSb?<JE@CQ $I}y}$Ib2૯j|}Z TUE"aE)fNA'XOi)\.h4vR\,@xlFRAv4a>c8h@E4 zms5TUl6C"@2V& |y0M^!Z?eYF:4M(jdRKhiBd˲փ+`ɓ'ܞBK%If^XV\ICzFRjƒX |aq 2P#B>%3QDQDRA>fWԞzksv/kul6c n1 r~^͆}X ٿEC6RvMAuhNhL&1xD<۶1^4۸H"EmQh)RoXl6 >5ypNj}??jq^|^nh^~5s|N?~^F(b0b݆ j3 fC ɐH$P*0-֞`\6M}߇EA#_ȡss mX,}s]!0^z0>ʸOS8d2| 5P;!7h6[HHmE 6 E 1&)%T5t:ll6`&77fFIv-}kr9vw|1 &\.1aG)ETBTB,(vH+ f9lrD"b\.LFETb >9Eٹ_4X UG @$ssn#YV# jHRhErk?`0 +H%i,آX.!iBGZ8ǃ7774-\ʈ1}]9* !HL&Ya&;(x%8#AUU\^^r,NTzxxj"N$1w²,/ł?Sl-fLdWf9!ep}u}+rrm4!bXpa,4 AliW2 U)RHM "Et J?n7咃!||Em>t:t:bl6 `0`0@,5FC.th4pwy1ԷR0Nmf땖DOR!r9cc nc ^RӧON5,\7Xuu $,B\Sb-j =qP.X,HqAr\"CedY7M(&Znl6Q 2l<7 M/j0C@W^rԅB4t]V!`1Nsxi2Z(!<l="@:e( ^9G& X )y+ȅr!,sNЇ!4/\ 2 ]qrr0!lnz,fYJ&Ih4H&vvA}^&צR)z=^!\*a?#f3^{v:. @ eaJtMtDFc)RH1EH"ET*%Ghr 1\:...8A^H&x gC{kVP()i<Tu71%1*s#]E"`p t2 k? 8}= @`0@|:r9XŧTGv Yn3[iBQFXPdE:>$w]ػSܣF|>ώD"fbȃ7)]CXDkc1 u8;j\ܚ@jL$5[ Y<88z /,˂8 l6G LDTB2jH{^u]>ia&łO>C8k80LV縼di"^v 1, X 4zN\'IQjFBC88%Ty||Bljbh38b6**׻^__ömbj% -шZܒ>˗/jku@*t %!`0oe[M ?+~)-l[<|G p}}fht: 8?k)R6EH"E%M&ӟ JlRA<G"w' AI0H$0;kwNV{:b61@Պ] dT Vmq5i9l9eÓ0"ӵQ.<W|:](v1M4$IAxc\²,r w IDAT90O.zf&X,ٌXA A@En ?3 Z? L3d_4q"wjc!'5p2OUUܿfPU5a](9h9={t: pxx{u]G# h40yf( fO F>Gd2b-*˸G;dQs^P%(= t+b @dhd\bxǏ}^(tL&䶺wlxmojL&skXsn @V߇iUz~X*/ɨ zwz>>1$IǏ/^qi;b6T*l6 (ZՍlt"dHrl9?QqssbOߧBUUz b0/}3ZٳgH&Vtzeϝ;wa:$H& |טfl u9AQ Yla sd2@bUUa&/Oa #͂ i@˺lUQ>b@&%AEhx S$ApgRd2HӸ>V* %  @K=HĎ AL&N!I;/2@?=˲^b1`k!|G}'Zpeqt/nw׆.:lۆm |/"s`>3DC?gA`81}(%(ɓ'fe$-o!2VZlh4BP`0tʼ˲( ?ca /^@*, N(JpJB?E$KE^$q 2 -7P\OdZQ~4)RoPѢ!RHnYǭә׃Jṟh(7M| ɤtn[ "SÀyl6eWWW\ٹ^úĎxd&8h4a:MzBVU^t3sbău˲z,[o3~0.>Sy>vxNA5@ٹ TUv5ض ]=v]rH) B$xRCiUVX,Bb>d2EC{^ڽA`0`n K߇XVanYF)7h)RHNo~[9bt:E"(j-'PyX,<'''PUr4rYQSAYPUf~B:Uf]5 "X z6 ;qŋ, vy. 4:Ja6a40 F##`oo c2`2 "a<CeV&%:EX.$ 777 Ss(b{6qHM(#)'CF7:OV~EP]rd&YP8* :dY桛KO[}<| .s=<%ǃ$͆/%!#BX mcݲ`821CuvDнe@jCAϺeY H$8i5...8Bd2Ɏz]E,-hZb>s&Q,y)jZ-x躎d2x̋Ab5,[Y4jSEQ,vK}^.cfD0 Hb?YF)7h)RH V?ɲlZpmt i&t]|Nyj_~On&)6?~̶pb8.,1c:q l;aj >A9͢\.ò,,[dZm=h]aYct]_-!(i(hZ n> 5 {=b ?-Q$s.v񜣣#8Ëbt  Li5! X,Ⓒb2Wb3x_q}u4!'rFZZdYt8ʢ!˞XY+vssAAEjʘ~YF)7h)RH p>|躎rP[& %!8kD򫯾6^wO-kզF+\\\@z=Q#LH:|h6EJ/^ wt]g>,b8"p]aslB48v4MK~W^q( ̦i"˱s!JX,g;(v@q:&d]!"* E$IN pd&a(X.<@{nj gYwFceP]zFՂL&< dB=Č|^BZ8af2O rYXL&d2 `qP.1fޠrl6@:Iϋǰmv咙JzM rh^Nr?7x8>R/}dYt:zBEQ8c6ZxENR)d2(۶9D, ZBEwl\.c0pKe$Ih;M~Q\.9ڑNQ.!I9lF">UTg)RHh)RHIzg{?v] u=cVAZE۽6^kZ9-Bs{%Gw2@& U&)ʢ(~ [9\9k-Vm)Rd0ω1$y.<PjER0Cu!H(bZA~Qsn[Z]ױn(x~qN,T*ttg2*^|g5l-;L&Fq4!}躎Ǐ34{CRmej5~eCv l A Nŋ~3?kA^EJ=R`64Uc·eYPe1u]?k:$7ؽAqZ Ybȋt:# CHn0 }1Lpvvn˱0 I՚d<r=SP-%!8vEZ(lw]ShaCN@ZaqK*LY!"4,bFoƉDA`8")jQD.C ET*EQ*2 fGtʲ qn;c|'t:nc([./^f{auX,^EZnkj5'%W믿,#E-"EREX"YW5Ei8;;v)|gEQ|Z.G"@T1\e'}~tDvByTᐇcixBgXphB. iHZ4s(B 5}y*6:;H9NOOd0 14:?OՊaprrdƒy<LTx dX\.C$r9ܰÆtLdm G&\siY;dYh4B R`܁DZ88{NOO9n!2t{;Y4\bh"jX(H$Hrò_ضYF))Z4D)PP-},("\EZj68VӻNM^mA!/_ zNOO1l{_;i* v4MC߇X, ;|M罕<@XdzTBeG FN #%nnnYVZ2\.3#^c^sjpzzv˰<0؎o%}BP0 afV\^|nia^#Lb쐈ǑJ&'BjO>_CVcZu<,+Zg- %TJv Jb0`:X,rTDub<((8??gEV64 Б"`}c&{Ct:ϚrK\!|s-=, j("d P," r 21ϑ9'(| ^x~ErdYnԠ9y_(PV!NOOqyyGHR8;;Yr@w]OT*;zo;.ٳgf}Nd_! pM<jX.P 亠H 39P'S370l6(4 <96F/(vQ.8DQDVU*_ 94%FnR5/%߿VbeX0dI",˘f,ϔ*r\g<3(u]}|EEf)}X80MX) j2pp8nC\O6뺨j!npz|0 c>4ANX,Ʊ V*t;Y4*+I[A?DRׯ>LD2D<kR+N>_"E)ҝ(Z4D)X̨+yq?"Qr-I:D[.?ѕikDQ(AyZ tqZ~a"uhd2EL8F2Kq] C{iXHRv-OmO<ŋT*0M0Nx nnn 2<* dY[**7 !SnGPYm3lEQ8Aau]lmN1`5/_J7Jh\.ׅfYyL ƙH$PVw[d L&Z,hxfËxRq,KLSf3i C]uaKc9p?s"4t$i@89`0%_ C\__#CuV+ C-`|,d2\}IH`6vN(|%vyz L xq]AG.v*11DKb` 96wr8whh^m+AE$)8àN]SNT04 #)R;Vh)R@z{S(Qj ^ݾ։B!l6oۦe ]l6yplk "h[\%*}ޢR@uzBVid bRewqO/..NH$fQסi;/j|G.:P5mqNNOp~vNM^DJpt-Ap -F>C7 Bl6lt !;!v4fDQDݟ !<=Hd DZ( X8n| IDATX[1'M|xWWdD(\*#Be²LȊ`E&8hZ*j&tP(L0!)3Y|\>`fX<U!O@62 FNMTHLˆöl4u9YWT[;[A3MTqlPd|,Γ9O Z.aZB;P(`<Cw؎P,`8B4qXjfR@f,TqJ(00 9d.KTUnZ3#@f âiaPiL;j(\֛oNgP*Q(KKYH2M\\\ۨ2RH "Eϊ/~z3oTn6W?k~puKQضjX,pMӰy:b1$~eXOɩ?!Ϲo20DP84(va&6 v'''pj$Ixma!8T4s:hs<t_5NNN0 (O|jSz a){6=noo.;n6>NǁiA EQpttka:v]sk| \__s%54}!'pAET*~rCM+Nn6 knrc0W_eR)}͆A&9 Gl\.|> t:^5XAEmۭbqpyybAp||xAm0..ήDQl}ܻw/^@JndtzԋŰ ^`4!2~-,bHxiE]"T[[ eC:˲Zӟt_z3LbX0 "|T kC"&$N ^^Ej`UUqssEQ ð:H"E)Z4D)[ϟߞ|>o\.T*ð(J8pӻ.YVdk6a>=,˂x8ŦSi u[! @4t:kΞEP(+ 6I";<Ț1NN:FZx%jauདq:}/<]^Ef}rJk:9 .!5*~Jmw޵qhGIh40N0y%(x |ߧ( <%VFj(chZ`q`Hdn0   L[+4McwO&cla Tp.T*\.Cu7rx듟<{(=Rܲ8h4X,jYf}ȲI$4M KM8bAS\__0Mn%y9 TݚfyŐf+VŀJw$V|>d2ƍjn˰7[BuX,Jf5j"V,˾E$WqOÂ' ý#h4?YF)PѢ!RH2%¿>Ev_,LGOOD+ C@hbq rK -DSP@"@"\w;l-RgC&~~]ױ\.6`&53t]X]X,bpUawTU H&$El6(<(O}GX K?KXV<|b- [GNӰZ`6 mǏñmȒqQ% G׃P* K{h4M4ȭell6p8ٲ,IyȤmI8O1M]"eY$ ٌgEQit]GP,FWD 2=zhqn˃m߇i>y1;09\ JZ<|eCA쁔g R,yFFAu]hrf 0Βsȫj\yz||̑`ϮXרVmɢ^^y* l6PUAF篿~YF)XѢ!RHBEj%enH&PUeaۺ~7G\REf3ٲ,uR)skj >/a$I Odhl"N#eOj2A?JM6jK h{#0 vp˗px.;Y44WT5 d\J9:H"Ez -"E-h4ZJU"K£Av<^c> }n1Ng  qȲɡґb psb1s>=7 coY?,lfD,l6C*$Ib8::B>ĹZ2:,0x4!I2>/("I<σ <ٶ Qp=}<{ LL$A4<|V  lw}dYO 4XL&0 #kضrdŸAF\FR, \P{6͸̈́ ,D UU"4MJ%n`J 2 `H!,b? @üyhl|R)yf3l>Ў#0 B {l6hZx9/<|>8@"N-1/#8|>9!!oI`Y/pĄ Z ?fW$I!Xi, R7S<\.MPT0jRdxEwhxw hmò}ʲ}YF)[h)RHog''_bi*/^}zגNKWѸe&9,1bJUUEلy>R| ۶T*irLb0I,mOo)> `8!*7 G%-jTP<σid2xG*4f:!|GR៫Vh6m-tbM]>G&i<{FRNqtt Z4qʎ nUUQ,1PI0OZk˧A˲0,5bHӠM}Y(Z-86|j}X CZT*'OnC4t:is4 ^0Pjڷ*!-Ǘ"RY;*pN/wv@T*r A9pCdYn߷l0 "@/Bm(P9>Z1SEuimcZ}\,%H6z]}׭ hǶlp~~4Eu"E(Z4D)[{ξXWtHÓ(xY?dWnH$h4iAޛff<4 t:(d2(J,j سdOY0%t:ߗL&Q.H$x|l6CPO\|BUUNTU( |O'sggg<KҾ! Cf$̑8(5y|ϟ9JP*0`6%ssf;rc򂅜AeE -˸E:f6_PlSxoO߬%D6l6֐7#dPݸo[X,p]Z Brxz"vOp4MhƮUvҵZ-t i6 b&v& /ofnqyybxvព(4 -hy)t^tn#l64 ,#;d(a,۹ECV 2.1~:H"EzK-"E-VӻTJ&Jk\e>ko?xfi /JOS)a )<5t]eY6jda- X Z JT `OKr .p}}Kb| 2ki^b7d2mftPa0ɓ'] !|86 A\r,\983ϱnQ* &v;4 ^%qP(Bq8u{qT*^6u z;Y4|\mQ멮M_u}bT* x\..C͂6 Ef3!Jˀ@xR brd@ev\dYzgY 躎T*FuyH!0,}LSλ?|/_jB`HPx@4>Acj ޤ64yLRPU8F$&Iڶd:uO&j6v(JE$!HVAEfRd2 4Q,9n&+x!A@GXdxfh6zL&:99a-w݄t0 両dqXxZ \+N"wlfx< EA`J,h4Bl7md4Ķm,|^7 DQӧO[֮dh@u 1݋,#E-Sh)R?Uϒ?<}Dz8ݞNgxEb0ӧ8P?+fy- (XVHJuш^ <ۀ beu]l[X##*dY(FX ,Cux%-$ȞOvLvt10qssT*bJ]0a@ ^#a}K|w8;;hF,CP(ڠ 5ȲlsW^`0"ˡT*a8{Yjàx 9mClہlr9^t 'ɡ@]U4LŘ%A#Uʐ IDAT澢Z_hh4kt]v f3A:wh8;k] xfGu"E*Z4D)nooWZe5NL{0~z_2lL&P{5d1FP!*j<4Ue $IPtYA`:b\J4ȥiv`h")(8bǁHdgfl68шj\CcpM5h#idY~X0MjOi($ n10f#< $I9NNrhbcZ[-ql"b\!( t]\.d2ǐe/^@srt:EZūW 2yyV+<,K<~`Zttnx9^zD. Q)#AZnl6z 1xAi<-1U( * @ӽECQfX,kn۝2RHBEH"E#gt.˶ooo??U"h(0 kq^8P,4Ձ4d2 <|zZ${:i4 X d,x bpȔ}j|4 v{՘| F-2DQN]qi[)Aj>2%)0dUj5^mKPPLR0[,m ^V+TU`C"A< Jn۷PU恏ApB2躎tht\8L\.QTiǠ?7eB "tRo6,& Ò&hYd4MC"vEل~hMIjfj9;;!2%5/|"HMB<gKiP If/t0T*1xu]xzf;`~Po.N| eT*qb1$ bbȀL= 2NOOy9s'dR}Jo2RHREH"E#Ruxf9 }D"Cx!4Mt:a<7 L&QjChRm8??gpml='Wp8AZd4,D.ASƓĤ1)Q, &le Mc:`^A#ϣjq&5 @3q]|7H_27}MvԬtXq dl>GbevA:F.a6b:`@OMudUeAUUt>#NVqK|T.Cke\Grա6\.Օ /2:uٌyUU3͢ld<ضnrd0 !";/\EX$IZQ0%,B6e;1-&<σafArq^Ȳd2 QV!,2 \ׅ d2#<|9DQhy4awR(rGl6LEC^ӮVU oGӨ2RHfEH"E#ύ3uk]fZbnYt]Qv’N WC'''n 6|>SX,lZ8C7t>LEvvt aP(pKE0 /yztcCEOs\ׅyÐbu>ǁH@}qp}`_$ b:+xɄYarMZp=e7ljm H( !2M_-B>޲uW,K6R$ܠV WEEzL&lPQVQ.t^5Eđ<9#T Ʉ'Fc$Fnu"_Юv_,#E-Wh)R?BgkZWtۯ_(|]TuZm}614MV ~#&ytp3F#fk`1|p2`0pa۟>! CLc˲\.Jv1 p||fM 4UU fzFxy]`j` bdxY9r.nooyXl6, arTl`10i8i a#Ls=HYA @"fiH+ ݎ=w v9^84/ᢪ*7}TUz=>O&d2ĩV;iYqtt/_bymm3b2T*U1Eh)C -ܮ#'Bz=۶yA( "7@aMӰ\.l6qrrnj EQ0LvQ*ptt<PR{\T ,޽{H&B EIZ')R_Ѣ!RHW|˗>ͦ.//[vf_i|Ou|m3 .Հ|oyP*^dZxX6qQP*JPV_V ̑xwEJQ{0 xM0=iT*LSnjݻb|1ʟٳg|L=A^!IeYɟɟ`0n#Jq;į~+躎sr9d-4  ;S;-zn&JΌ|IPT\.11!0rd2 4 aѳy6 ~Vu0C4uxvv|^Z՛8 -dY9.LػGNߺXUd/ܤ%X-Y:&axCZ|'Xld>iqdlVb#K{4;RLO7ݼUd,VCwO- geK?ٜPV  w]xl6 ]ٲ, H?hyrt:&dY&q#( \],<~O u|;;;ds$ 4 hvMƙ:VVVHNp8z1h(C"2<ݻGV`]L~X wm?B~h$EQ)@ EQԃ*2ٳo߾]bH$x'vL&.,0 lLù\UUt]w4H$al6Ir?fhxg!2 /v"mF=?+@vɓ|Yt`Yj=d o%G!פ:AVap],˒l@ Cy|$|2͢$rys L?_I_c qPV( &'kG8&GGGeb<#B40 XB++d@E T*d2dq9uy<Ej5 0 you]'#1)O dII1]] R4!ɗ$I r `&8du&r!dcIۅ(`Y`!LlnKA7Wc.ddt$,DQݻw@q p4Mw:,,,o/~ N @Q4 D"'L&t:@" aFEqFmp8 0OeYgTJFZ\%yd$`&$* PHyD$mDH:xSx<&c-g/?3a2rɭV64M#=~mNP(xH$B>HF8y8Dz=r9pG)-"7_o0B?EnǡP(œ+_ 2FdDz,M>(iXYY6y_"URh(N~oϿ˞@IUc%0P}CUB;;;"Iv]W,{3MDygg{oeZϘih4p]vd t]ڶm0xIu}{y!">XZZ{w\StgA$r8"jh.4Y.w":.s:P?,¨( *6 Pa,,,G?Xř3gHr_bfrE%0;; ۶QTHa2P(Df!Y&R!3HbFoI%Qs8:T*e wB߿l6 0{w@C2]uWEQ "bC&m|6fff00 :龩T*13U.(%euMӋmCQD"R4'  H0#DQȲ ۶-84M ,'^dDY3d5aHPUd2]eQV=I?w<t077=!Ki, iΝ;ViYh`0B+WٳH&0 '-~P_4E$A"ixꩧ<ɪ/dhȅ")qaP hBh4rjEՅ gR,.~cii黮.+T\ P(Ҳ'zO`& cYW$b1|'U*ç~糪*kv_;eRhb`O?4=g˸$IdL&h4`Yym˲ptt`&!I* RDɖ eIC(mH&Pe!H˲ߺOV^j`@HAP(4Mu4M躎Z`0˲PTS{AN?͞CAD߇뺸pUU133Ǘaxݾ!vra5}DXMr*DQDVk8ٜ-=~CCT@p88$#@uxGlT*E.BEрx2 EQP(P*2 qI!y)ub awwPHB@@&Fqgw0 CKgggI`0 TΜ9C>'O>$!(beet^#Jq!<<<j5D"r.5h4B:`0@,k,"AL&1L0˲H/b!^C2Ρ*=Zh(>r+/_B!*}95LlVApB@@ZCd].a DQH$/ɐi d`u8,\.$0 @UU2HRi8{, L$V`w IDAT"JvIP,I8_{)"z" uKKKrjndU\t B<78(MHC4E" HH^z=;m#affaa@&Y%UUUvI`tJH C$ݡ^ncoog2Mt\th`r9tj`&6"v1!I8iPUX vwE"dܹC6a}AR_G0Hy*pS? lٳd/#vnw9.@{9Ȳ ]!>VWWd`l&ϟh4"ix饗JȆ?#/' {Ud20OmeYI}Q& \x|S'E*-4PEQB gbuu?HSh4^۶0 <ڙ3gna6Rj q}ȲL~b9P(D.:~:+@$0 FQo,..mmmQP*5M+Jv 㰸X,F07{&$im4FxI0 N# lbqq033Cv\6 x$Y&(nr|\d Q4T*4 NV+zwhJ4pG췷N:6l#0 ҝa&yb<;;I?0Fd2xQTpEqݻGTUE߿y} Rd<x,ÿr? 4Mp`YVtA@eL&={l8Ǥ@eb8 fP(h4q%N::\EZR(yJ l̙3pf~. y7n CcE4 r>Iv1Lx?wu]ri_ZZ"c Q N6aHvA6%k\L @:F8Ƈ~H<ֲO?✪0 /5MP|hzާ % `q 󀓯i?FFdDh(Zh~!R%I 5M/<' $I$l0B!HDR]%?TXvo>}2;D ~Q_ Bw>|N+h;ΜY^,8Lptt@ B@z=g(`l/jĥ%ҪQՐfq}hF?׿dxagA\(zL&$u]b1q2IQ4 cyyAH1M$jdta4.|>p8~O8b1p@ Jn 0 /D"fI6 dY2<锘DQa>cAqqi҆FX L&hZHRT*p]Gjr(hmd$m<䓨PUl`PO:Y t]G?8 Wo`bp]OPxhB?²,$/~`8V7y$v vIa?O(Mi"A4)">鏟ht%EQ`}tY___K$d2ْixh4ȼ0 ٳgy2 ۵, f,nFXD׃i  OPmoC?Kɶ^OpiCCH$888@2i 8LBF&rkDTpd2`QͦOzF CU]&qF#T*G8~\1)T5Ri躆]Ѩ!"QT5]#v<Q5]1c s?dIDBXQ`F0cR7w$(aG /5U}KCk,c<#c0 )Jjvq"8|7"ԺJZ^$^7JFQ0 F#= 14quuuM+qt],˨Vm cooAUU])((F³ϖ`aaz0TGٳm68 9b1|X\\zR9zΝoj5sگQTM]ןw]W8Ǒ`0 aF# ᰼jt(((B;cbQUU;Ht}8"LeYܹsd333`Jeڶڍ7>gimmM|KEZ-j ^v]ױx]CzڴNQEQEQ3gth4(Y)]}IQEQE=RŢJ%m W$wR@m$IZ8(41177Y1( !DQD2D^ #޻D^m[`FZ(2ڳ߯FQ-4<&^~7Sz(B<Gل(`! h0 ۝w+[[[i{*x3EQEQE"Ν[Ly^VEL&g&˲dJ*( ˲ H$~!y Cض q h48A>,p|3(: ̍FLiY@ ;wjayyZ︲p񉋛_ؠZEQEQC7}ud:idYF"8REQEQښZ(d ˡh}t]n e$(xo<_s]\+lRIմձi .pxx xG 0eeydYT*lhx\z 0GB1 W, Wyk>it yj4FvOփp&Z,jodEQEQCw)YN 0Y7vq TUqD{ 7nW4sYV45[r%˲*,˂i`Y~sh4 <7]/^ iV #Zhx;W,Z$IC^׿u NW?צ{uk( ,C!p(08v ((V wbX6P(@$A ۶ilw~z5 CYn.մ{"(}j5H k|" RdS+ss?-iB X,ǙXyOy,CR)0 ͏ޘyO{vjW\6y0 iRYmeb:k:4|((z(\rn߉DeYu333T*4 eYR) Cniu*OZH&.8 c2u]Z-ì _YZJb/z-R0 (^n Yw>>*vw7z_UX /Ȳ QWOEQEQuJޙU5M`0 & ZMW~jj4r}7Sֲ,2 00  a&FߝD<N/B!,dB*zpXo4ZѪghffvCQ¯<^G*]um((wRI5VWW~Lejϟh4?4Mkƍ=ԝ1؈F>ml @e88h4g0 ö^V{_iD st# *,8^\>pS=`0&"FlFE\anV5@QEQEMuս= eaL&C650 i L4͊gz4Mt]`B4b1R h4kM<SnuuE! u]D"TUB!p{}@s>'+Y}N IDAT+g7YZ^WeY d22[*OEQEQw pHB!b14M$I Swo(k͛}7-0 JjeYؖ4Q<*t]Dž 0;;+y%pe\] D"ma2`<ʕ+8<臇Wh󳵵enzoaV!Lb<uo9EQEQџ eL&l6a6pme]?sfi!'^,F@Ez=e>䥷>*4S.L`< nt]GOw}B}iii۶ϵZ-c(8tb8BU̇ޙ>#p9sf]_4 ~p9 1* |pZVجT*"JQEQEMկzUuz+++`N%۩d<͂a,XiBF eh6!Iҹ{x~dB)sϦi@E݆mhhZxG?tOJQEQEQԃV*T*'pHzL&r8vy ۷^Y?k/ҍZBu2^nYxyfp]FC\Y9{R-4REsjgp  nChrL(((13??GPhM%z$Q˲E0[nݪM[n՞yamff~Fq`Ytssst:p]W趸;Pv8TUE6E0p] @{?ؠEQEQ)Jd2x q`r"&E9~pvٳgNq7o⣏>0>N.m;w( E$I8"MEQEQEM,r9aJX ,oy~V(ްmX GGGǸsǁдg穥ҹtOhT>3 e^CQB!a0IQEQEQԃn+:4 |swHw3 am#ҥK8::B 0$ x|~hG)l6U177W^y`| * <7}N((ϭ鬒F < .`2`8l\9r CqHyR)<쳰, (L,Np j5 {P(d )~ggƴJQEQEQԃU(_yp8$I t:b}szjXm۾*aܻw na Cn@_h8bX1""u]|`Y@,4EQEQEQ!P+333d2dC˲F9nx}|К|,B<d2A`0@4L&:>h'|h4 qdppph0 ؘ9)((zTU :DQD"@2D6m+q{," n.r`0@tgNh4eHZb{{o'|p0#REQEQ5,˼yx@(((1S,K è/_F$iH&F8::ژu]Hh4/~rpwA2(~~1GGG( y\  (EQEQE=HeTۍjr"EQEQE=Ν[^K$ҿiuujEm{M ֊EWem*cV X A4 DxcFuyb6rA y /N!EQ$0 MӐH$pw<REQEQԣeu[ y$nP(F:136R(HR ͢BdY0 ۷ocnnH&F0 >#>hF)L&1I'%Iq=!EQEQE=ŢZ*]N2~kmc4l*Q.1;7^Yu]ݻb:pahu"666~_Swwwvda0L*!ƴ(S(J@`0H7,`Ye((_O[[X(u4M$IB8 "0q^zyD.4& 4MC-#(B$RUU# nݺF6c=4dY0h68<y2YYp䚢(L&iöc, ^?H$ִ׮TEY(O@ahh4_vÒϟk'˱ BӺGܽ[,uı 8( 4t]G݆$m^izu&?v! ,P4 ˶13; pKʰMOO]pq8z=k_?;AaY9G42dY8&4]]-~kq~REcuh^ё''jZP@ LOOl6s1^ĉцG} w|A(Jzà) `nnmFai]dgA*˲.*H,*_\v.!4MMӘC=~˫~$l6[,˂(i^4MCu(e^TôvT{qk!1$D<˲h4, F,g]C IE0 V=yy5d9I@ >Oy{ݱyjj gYVyP5i6Zz=m^o1@ ٶ#7yQ>˲0M4MdJR-ȡ7tW%u]G$ywu%~YA(t:t]8f i_*m~wq~~WgffoF fbbibzzwܹx°oDvq^fai8a>4'j}AR(F\Y\_x)I޻1]sE &IضR1P|!* X8.OscXBAC . @ORnZeڵ/ ;vކ(f2H0MsmڙQQ5ZM OOaTUEYQ^~Pss\^d"@VI\_Gcr>9]7/Z-8Ili}W/jQp[n_u>z&6660==P(H$UUaYFGGaV^U%IX}™ܰ1@ "A(*t]0 I˿9t~kkK~N8 ᰹HfAQ=40%q055j( LEmÎq̷y8PUu'?F? {XXTGm'gxv^F+g~n433/ZMH8,Si2z(A8ei0M#J}d2<9+8^hR'p駟.bh4RH$I ! FGӹcN a;vl0={ȑ=r!LJn>&œ(gh(l,zjj?q,(B\8իņ0<433 c_vŔaK:@I :('}M|>/c*ԫ/oooZ-' 0 ۶( 0aǺ_ {\</z u]r94坩5X?{KkWer> 8i?477-˲uz=D"P!$80Eib{{$A,ðW:Nqir99*<4"2JMg"HζmH۶l6D011*ǁ, r6^x衇x?fck++ o$Ix1q]a`ttiZ~Sw=@ p4J1=_.yQ#qhZ/(o*a$MÀ,PU,qB!,sʕ\t+D"{88 H'&&ƾyn fADQD:F,CfS);*H4qJ8qeYyMv> @T;w~yرK^W6J|pك3kvL\?h4`6lF݆ BA "Ⱟz'  s\^mY b0_ёftdP,Ax| 0MfN;bA׭dpdPx-(e|֎=lf$i\E"nݺso@ sL&oYA z\p܂mY@$I4 h!H@C^~}OoJ˒$>O4`&\E:T@K4'ϞxyA_impU ie/p\4úv#lw,4X,rTZ5|\sǎ=}X<~^MӀ8#Hu]loo<ضd2R$!Icu]LNNbtth4Nv ]A4zjlPHz.)t9c#Z-@A04U@ DIziGݾѺY7c&I2G$^84 $q[W\}Hij($ "Jq;w(T}-(x СC`Y(V4MmEzx>;Aa(JJ*zePUu,2Qɚ IDAT(lj7)(JzO~aMʦi(eyǁeY(/RwK=\ieؙm6H In8Z-eA$dYFRA˲8Z"L(, Oܨ ۶Fy x9Qzΐ=} UݩrN<7kc "ˆ_vT*^[^VΟի\EDO>$I4}ўT<銪vF(ː$ iB$d2hQr,Z֬ ;B.'ˑHuer,ˢVakk #" akk ݻÎw  ˒Ef M IA$9*_nݺǿ0LT I<σi f^"l%I\.7t: l64,ӂ(mC4 IIn ۶Q8bDQਉmۃcC4, @UUɛy6L"5EQ»2isn pԩ+ׯXv@ F<.?J `jj GAр,* D[7+ܽ{>|8vq À bh4P;\G ΩSnll@b1۶!c׊ŵ H4NN'PN4d2C`Vɓ'i|T5q/at]xyp]R7.;gv7AA֋kHvTĢ2R$w$Ia|||m;K]d*eBZ5D¹R G򫫫Qel6*hqX[[0D\FӁPF. IR}{?Jwjkk NB8F6<Î/>kcc|"yZ$I9 oϋŝۅiHė&&Ɗ$I-KW^P(r}C$i>| i1o(8CElmU.Z>kJ}dĕw>-"2 y똜dniiiiq1»ᄋ58+IR_6LDםw 1EY=]Fqض͓b8XwCEypuj!_|[vB fv3o8dYax!!LLL J\.@\?Ļ|>qt ȅzpR̠0i,GE(8~zѲgo߾@`g<`Ϋ^a__.j6*8: N#7^ۦi"`ccZ<ϣjavvfmq'^1;+ jyd4Mcssi"i4ME' (;rEAZi²,V~Νa N3'N}; `4MC,0X[[C63TΝ,֧W}N4XUU!dY0/`2(|,˻snnxL?bd4y(BРss(b{{ҭ[w]c|>/dr'rBei<{IxwEgyBPX_X ¯LMM8( NT j>*a6ϲÇy]vEQ EQC ؓNOz{c]$lFCHȅ_^GE#=cF?Ih@ua4M$ hS`HQ_}l;vȹJ]!$IBF$dnc}RDUu;?vDusvy 2P , ]!"lZiV<`}}d^jOHRfEV )4ؖd2j]EiюvG]L&τ\wa`YsIj5b$|~^ƩSœeY.,G"$I[$q\uZ 0dAatX^^A}X Eauu*b($.W?}[d3 cc[z=eYT*Ah4j 0/p"ES0>> IP7o7eYt:{._{r2`/V(P(D"}\nP!S>,zP(r޺/|>/ˑy;oYvRaqlooò,@e,--aD"$ f4$vaVL:E4B#,k%oZ|<;qԛͦ@dtNx@4wvϺÎ|^WU`f0CQ/?b}}شy~VEQ IR;{.--#~'geg=}c||<71}me@ l=鋑Hۃ^ñ}mu;<7w{sXlp4c&JA@LA\iwoW_qË۵76>R,77orC.l6+ L&A4 @݆ PU4M#LB3D<799uR쪵D"qn$I܄p]T*!L+OQCߗf,{PAQ\E&$A;nmf3'''h͵Oɓ'.$qQ|?#<A b4#MPUݻųwAF;fg e?M Mpm$I0 #㩷Μɞ|Î~;`Ypts.7.zqyeeFz=ll󟿳0X?W[߱Rl6t:RW"`rrR![Xt5:voo,..pG|߿v(LCXā I8O,X8>8a1U((0 hh H,wQ^T"L65Fd2J JNQ!$IBE9O$iĒx_bYz>#v4OvrF 4MPUibll i RTn166v|R}iozJT\NEaIF@(BA&AD(^b}[rM\_eA0 #bl|ymڂmDC" <@._+b8 ̲bR~_ٟA$=^xŋÎѣyEieYiR)0 7o"L"O~#˒$}qPH*p'beeZ"m6q7:m ŭxo#?@ _gz6/œC`dAE xb0_ e5yGbq,G=˵[ 8W8X[+BU/K~UsvP###AVC9(h ,˾4]~siWhœ'gX͆a8v ]OWB<˲\k&=H$28666.heY$ d#h}lU垮"ZOj5؎D2P8m ]בH$):AUU$~^eYPvCggff~P,d9EQ|5 B!HR[C|y˲:,۷,lF8/yuqر~RE3\;K= I ]0lnf&wbqӏ{ R pjW|O<6?33=_iz. nJeni(* $IeYp]N,,;{{0]}G@ p_dY~xt43?>>~vtttpN?~z>E|2{6DQDZroft4{4zM,FGG(jCEZF˲ 4B8FXyhZD"d2H$LJÌxa&&''fn f4M[{oV*0 R\|䑓k<ϟ(J8<#Z*j*hF,m;体 IDATې$ ,:m8; a \4p]cll "L"Lbcc@eܹspM$)|wP?Ax;l6_%rlQFwޅ0 ccc AP,..į5]k!2x &&&tV' gjȾa$ 4l6?c;;#G6MJFH$Mw4/~ 9r"I{.t]G8Fպ|k_Wk}qB^GXL *$qb1J%H."hqX]]0p]P nX1;; >*K}9 W>4ӮGrA v766 |߇$Ije^JFR(]Ç΋m%ޞ έ3KKK?c=EQy&^:ho8:: a'/^>O~dAėljW4 ]q]1NI oo/;|~.g*hZaj(h4p]nBGtݼpT:& I˲pN###8nPH$q8&l4'J2 TUq$ xw܁i!4۾DKD#hx}'LӘ cD"m^ȷZsXz}"qBɲ,l~ޯ?PfYAb`[Vƭ E旗n<_lpmò,DQxGnl,Ve8]ׅiX__q5E8q7D~6;3MXVkƮ/||AVl_i㰽=(vh0 }^$w cg?X,N# `YxIla gΜ^pwaA6D"B$Z{ͧsssT3Kvyit˲drkkf\.y{'X[[qX__iWUUv, i4MBEmGɲZ^ zf,[cvM5]5͘$IiHRF&vZA%=0Yd<8}s}gӧOzuËZ \3bnnH}h$A:&'' :4M v-˂i08D=&u]@4Ljo,3^PHZh4t:p]uw%w&$8krqq%+++rlvCnڶCP3Mm NC48۶133$-L}th4NāOdYKH(d{Vej"V6}}5+S7kgj׬Г5̤@%J<$@ƹa_JĒSr\"_4AhZ"/=vW~t:3(.|N% +DeY|>@UUpaalnnjas3^CD"Џ*8hg~RҒzahhAT*vcxxfw8OZx:/JWVVCݵZ[oYAazl$IW+W~?#h@۷HǝlK|=A^d2fap0(N'*2v;dYj:hF^B^$˥זm9<<Xl6h jhh`Cw0o4og^썟(۷W[dzz:{3###o0Hiyzz?ϰ—Jr4Ѩ(u:O~O}7 ^/ ÀhZV iA4'hZ AP I:HD^G>G۵ ܏stt0ZcMW5M{eY G"ڲlׯ|h׶h4*˅v;lQr( ;l6\.A%eYeTU4 ;aKj\>̟я?+4M/uApx9>N4zrall앵5uffL`xϥ*|+^[:DQMӰl0r\%H躖l)iãvJyNb&tOeY?~ lxT*Q:80,x^EQ޺phffj駟~(aT*?l6.!:N[h{{J.߹;hgKS$IbccEf(+0kpB st^o;~n 8l<3gsv;{ll [[[jil(% [[ĉcz [,RnQ[oo033DQ <\.{ڀZ{EGfYEYvh4q"t]8N']v[I>8APqa.G?J56//xd\ᅬNJ]Q.LЗÈ$ǝN<˲`v,mA\gffr;… ?y3<^/r5[LlS_N$ݳ>ɜu_җgR)EdmnnNl/yQtG?Rd tvGI8+zxx]A$nݺi|vƾ斮\w{Kx=} ~=i׭<_Ocye4 O4\.WUť\>o?#E$nܑu] CR922jZ#Glj˷<333tr?ޏ2(( Jm`/ C#jM&Ƀ}9{lV?*n7&&&<{{~_K(HEUUhf]4\.x^T*9޷K> vw+~3Y~ xY:?9'k$CC7fp8t:^O{X,MNMMn݂fIRz(( 70 FFF^g$8d2~J%U$z=QNmm.9H4~:ʇf};?k ̙3?r?mCC7?5n,Bn7Ţ5Üd ZW7Z[[o-~_eqBePxv^OL{{i٬p%qW,"J^t.|&zf$0 躎{nF#Zqccc lfN߫B0ы_gEP^߶ZSZ u{pD8&XLj[7:z8BZVV|Z@_t:iT zڰU @Aل(Vly{M$DQD\ l6@.C$ARzeٹb=Qtyp8<`Vzk5BD"(Rf7l#\4s~?ZEnGE^|áDQD6})JcG&*&ni#nYΥәCuX$I'yZ-躑TC=<7wAE~bӉj\.b+ yE4 94ff P*@Qt]@q`YU%g ܯo~( E0`ż4}'ڊK۽cgI2 B" EQ8g֭[UE1rYUU<b'^{|Jܸ޷Ù,JPLsnu >Qa Cua%8oܧA/)Z02l61:2B3TP0? syꩧ񗊢{X+ eY϶Y4y>f{fIwWo3BÀJ6UZ_I{zZQ˾Eٌ 92~Fqv#nq677hfPVuf!n^ l`Y{0Et:is`T[P(a˨rCs·}~XD"(cc$eY% v.R-CHa#l7Hn& |}d>@UUvTU~T*DujjjeeV/ݽۿtzmlzz;\.)ng0 &&&$I:R]]]= vVUJ%pf>]?^oeh*fCӁ #A* 4MA8nU0t[R$jP-$Q044z\>|vݨwddbizfzllu2Zhr\,~Qf\.әZ]][FID܏mZ`|0Nt: 5mm$se"Z fn&:RPKwvTE0;MjQ$.7BCollAH$˲#q<&B0Z6l&[$wXbjr`0tʡ0afPhT+(wnv֡yȲZ ⯪=)MMM^t8TP8h<M_QHAݎa\~;g}w=.ktP,p8y׊kǏO_bRXz=UUE r9ڂFjj YA$( BhU8]N~ ^9F|T.F'''Ee+:::u}֌J$` VWW122r[Vy3bt:Q*it]AhZp\ Iwywu~rY6+nw^e@ U?4 a`z122}$zie Rbi?I4O۝Q]t]t]H z=$RN^e5p8N !ˡ} _׭Q+QYecp*hp(\0a:A(ulrވɓ'bgΜ\.z=(evp0p! t"tZfyT V*֯xb%^ IDATP,.Ñ>爢(R.GZEїs|n;t$sm4f $144hԫ穛 0YVYs ,nz<u̙3۷oH,9^bYVbx(fϞ{sF 1(4 ,0?4 )eJa\=6^ӅP(@EZ-ng:.[n.C:Ξ= A` o^~cAc/e{If~0PU6 `es:Lݥצg. kګw@FZE$i`Y^chhlbccpBf?55 eqYj5.sn<_ B禦fO8z>oVӴX݆E0zrp9e`7,|sfDQD(B^v/˿A4jFSnQkZ`Y,õReY|+_^nGXA}Lfg?ܜpJ$l6QT<|>5Bx=ϵ'8{'J %~˝N粦i6s-U*8MOaA%v4 JR z]TU;vcʄ1-0 c^V*EQJE)Vc X,eYz=loq3 IM8z(4MrիWҙftfk_KSovo޼~^LLNHV+f&C^. # ݻ{{#o.Q>HdYVp3GM?bhؒd^9r$jF=g2@ _NF*g'yǢp{mhfZhMO!kV=Nh`hhV!X,%ƾ4==d2y_'OXEQD2nZ ~N<Mۣ捈F͛7166x?wܹsuck[1GFFM/IV:^/Z&L&!$i<ϣ̀7[0o-E:%:N:wy'>/.1N'.WLvAtJ/7YyqAfpٱ:Cׯ:򣣡fb$[@E^ h4(#sFFT8:Żc{T*DP0ln^/IlVap cxx,\K33Sd<{6Mwc~tUPU333i}t:`0^|>q ?~Bl KKK0<;;cc躎aܼyo.PT^f|24Mi8/P콾ͯ/V*}\.P0P AQė\9؇Jd2( h zQ,N//>>gFG?^/Jfgg奥>FR%D"LLLH&pFbmmt yP,v.fi_UUEB!ahuJj{.=jeJ@XDVnܸ @ $I*A|>O⣏nȍ SEQ6a,uPݏ2b,zh\Ndz155L&MtYhHY5:,XE( XYYðoܸ_;JNǼ^;l64Rx a`v*hr;Rx5;QՒv;nFkxp_uƍڂ׿ulSڂQֿ,Yg'ntߟ'^DQL-zXѨ40kze/]ruZ?I,uʯ̘S0ą@ @ ^YN^Lf Ȳir9`aتD"qχXh޺/>#eYm]+FeYh"?×| p\D"VxgdjP^z7 ?(N Ài=f~ 𱴴$gb]i,ˠijUA4Z-8qAF݆,˸sN' Ès333󘞚- "NN|ʎWUt" #CE8Z-l6/mogAjY ,nckk vνu&$?8w$`Flzzz\UUed2H @Q>?6* jX d2zNZ3*ay;ȺST"_mWVV( ?U?3:ygu`}}UGFE]EEQnذ m[^^>TﭏH$gcO]lg~2HbvPzb,vn!ޏt 2 @Ѱ^/mn0<< ]!70 0.*:$aQTP׭!d2_ K'1`7p d2m8,$lsD(0@2.?t:muQV~7I$#Yl6199j\.t]mHݵ~K~x}|*B\$I|MSl6}|^x4 ̬ndfZQXlv@Co?4/+8z({9+nX,fnVYqVѢhX(Jxx I6(&Bû){qE2 (jxW:6kkKkA^xh6zDӉ'>Aa=h4^fiZTE0 rH$bݒe^.zx-UU@RA>\.!2c\.޽ χwy4MX ar!r?$ ,"͢lpP(y'4ZZ>,V"L-mnnvV}-_(T?(dD 2"ݮu+`:kt:1Hd͞WUٜ[s@\XaߛE1JT.n[nHZP9a.}f„ix< wvv^8$Ij̦ꩧH 4(dY˲( |{yzT*}Sp?(Rjl6 vvv0 rURI&r6_u:TUe @0ah" v(6 }aᅥ5vBr\?  I4mjEz v fC(B0,˨V`YH\茶WiD$0 z344\.r  Fo1e.4MIy螽}{E>O lv%-.3722P׃"̛Zf 筯A4A E0p:31j6:E%|tt:v|\9αP. :~]8(=J:$ITU3`rrn Ki,y5 c63QH岼tm! (&'Rׯ8t J,̗e4M\.t[~NOO}4 . v> 9yD$(MӨz J,vfCw~v߱$I^j4}YO٢<1qZ&^47M08q 0(4 Ȳln&\~,xl~n[sP(ϱ 0C@TY~iZAtZcA׵pFHDXE,)Y~pښ|R>F_(JeaPUnA$I$It])D" rYybњ-7#DK˲,?T. ߏ]@$^h uʡy. fv6QrygѽT.8660lTUU$Vwml) TCṕ[+}ӹhx8tmmmk6m4h4|p4l?M\.eY4Jt5_DH$<3Ϡ^[k)JR?M;fw;>>M@"vww!"anGEGy[nr` 㸶o9]͛~s8X3Gl6*EsAm reyy9 `A$Vck$InZf,2z( E0(ցjzд=CH˅-0 @HdCsCq&''SfsleOmn&-b?n/OGv{LQi19Z D"HRnŢ{]-,BulmmYۍ|>Ph^( slvpal0 B!PU/~mPƤqۍZ˲ S8]J%4Qy3~KD}Y;r$I+*]=Qw{KhTɼܜrZkv;}1;ͦADӱCA(n޼ hȷo߶ # f!ρx\ahvާ׮] +MS+wwPz nݲ:4Mí[ y<4<㷬b?j/tJQTU4M$L&A`_觾>o ?$I ZM4MSVv;<Ϟ? #rayy\d2í[022Y L&.bBW|d07ͦeFd_?w},,K8.tbn7 :oիpl6$I2r"Zx^8{a)GPAiB?P1xmn# {(ocd$Vy###p0 gۤsgn}{nrbbvn0 \~|0 7diX{A^/,]oxq{U"u4kkHZ ###壤(h=L|NBZϜ9χHh4}Vy~+aT*AQܽ{(tZ>,D77x ![ogv\> (D"{oޕ4M*_׋!36:F~8>n6QmZ|>i1]B\f6籺z?atKҘr!I6m?ЍMt:JB5M$IhZM(v(x080 &''An7Hn^_ZAQA nݺSx'. pU<BNjyp{X1kN'nݺeu f̌e\VO[B^3 yT*<쳘AXEt"CUU\.\.j)帥R=i[9` `122z(J(X]]EC:EQuswӔL&~TouWi,˲9:bG޽{Z5R[+R.nq2+&\.N'2TU0Q 8~k4rJUUehEɲ -Ep:(X^^ƍ7( Mߚhz/ȑ#yf\v{B>@ux</T*zI IҜYN<6`YFfYðlhZ/~N/ tڊ&V,$IH%"uSSSV:|>IhtV>0`EXLA+lbggviϋ[^@drH r8/'?=V~р/EQh(pp\ʹ8"**Hi|hۦo&PH<}4ܹ-A`ll T*B׽CC6a CCCwpv`0G}Fj(*w] D"Z<_/-._s $=u _}y{QhP(N' DQy$ eZ-(^ DE_eQKRU+L8{P<|>t:UːY{X,  Z76 (E9=l6Q,Q!"6XCqbrk6p8ǽfB=| ]j ]ױbW&ƱWdh4*EA.C|fNk)[^h6 dfCCCHӨT*V*FT|ۋgggl6e5zX"螺9`v dYr9R)W&'^6 Xő#}l6n8BE\BNEV!BQvt4N:SXn74MCD^.XIǹ>z+Xl|YrzfhZeW <1h4022izJD LƫjZ=T*] #BÀBAN4TP(f:n7޽+B, Mhh4 r @EápY.JZ <σat:<hŢ`3 Á`0U^1 5NQ~?v; ÀD3o^pow $It:nn~[=株*ZJ6666*$ixB(a$ fǏG$A:A8q"ݻ7?8w.*[a!LUk4MIVWܽ{sq^8˲T*4 ~\B;;;H~k4وV׋v ݎ|>zb8`Π0`!Dv`A4σw:+ET*Gb2z8p<߾{A  v9tPVi:TUɻ~8.D]hbT*%nm~osرc$IjE:ZrEN:C NPnY{KNuMӠ( xG.Mvqss9"ISG=<| 50`$0A@( ^R ǎ. V3@DqAaCAPYQ.a @ӴtA q(˲l(AH[߽$0l6az5ݎmdwcRԩS AAE{2RkƢ 4 ^ àT*p(zݨ$o ^vv'OZ-]7- Il,ڵkfwۿ}x K6 flbvv<AIٱҐX# /v7կ>|/^:}O:(*rl(J%躎@ @ 瞋Ś&8CR>N~Ť 0 ~?,H$*Ww^\Q!xC)DP@h4=k}FUВl"@FʪszLӄ԰udBd4_aYP(i<A0(lGC4 pݸr Lӄ(}vf>.bvvNZA1RmP(  t@DQu4/ ? ( t]q Iz4DFL&BVp8BX,:lyR$8NX~o(pX]]E<_'|0=($ 8nW<3l(_FΏJ8sss0MV vg$E EYIف"+v73l}'h_F|+_s簽z BT(a߸\ԺhPP R [X"Ed0 aT*t]mZ>?S AdQ6, ߏF# OQEEQZ60+S6\.:\.8x<(F.V(.dYEQ˔\Kh\\\\EL&32 m᳟}4R(tM\P|eRDoI0@ p~auA(Bo|^hrh $N.uco/{7ll]haj0==0M3 @XE,ID{<ԡC?uQS⎢()bt'|0 (a,--veYxW<A4EZvZu{!i4~rg bll@Q^/|>"`n# SSSGYyt6Üph۷n]*x蓉ϲ, ^A$/_93lu|oi2XE\F>_F87X2ݑ\ W~zE쓲,ӆaT*AQ_۷p8l6mpyR&N< r'#ܲXUA8pŇX,B*z.`YTiA.JP [G%ȥJe+˝~}ݽ싳3)|>ZRUi`$X," BUUT*@($\HQA$l6166Icn*$s;^6GcMӠ(*iT`]R|Aрz&&&bv1\>^^RUwvj:H J$}7Cacsp[իҷH|amXI0==r MSSWnyL&3 /߭V'"4ME,TҲsdr6Nɩg|> A4[nƏӉeY0 333(z(z`tfȫp8 ˲099>r~x>qb_4~h񶷷QTrPV!">Jp>oM5 VVgtHz4`nrʬm>|Zupp~/rH$t<&<(PjmoaglG_J;r?'>&'9IpV=ò,8iY&A(Q:TU8~?( tNUgP>8r)l61===im66:=`cRX,4h4qܹx<Á1pL.WLFN_^^~fooN$Iĉh4ʿm`cQ 667ٙ3577K.aii /_FVñc`Y6EIbsL&?OhRifgg!vvv<{4l|&A,C˲FN3d !zXM|zhF(0M^Aavvaڵɔ2־T^0 3CQ$IBB>(yl>iF8(`PV 333h4|0\v"ÜDg3GX*" ǔ: }?^?YAM'^/ À똘䶶+, N7~yyw:몞t; ifeO<AkװJH$h4VfnooR駟aB̴hvyZ-;xlC}\ɣki$IW=Bev_/.dw111~$g`Y^/nܸNfml"脍/ǣh;@ 177470?~EQUUb1z=,.."H, -󅡷r7EQ.HeYuȜ?H$ S \.CEAb1HE^ afst`&&wZߗLT*411AMy]a& Àie,ߎUUDNjǡl4M')1 AhgfO|>T*<zXZZBTBBX~][[{?n4A1$I,b"I\.7rE_ر[W.$ NzEp8_(Jb$f -&Re^x"<4ݨ^/t]G, ﯭq}h~~Z)]jݝnr ^,A7 N23\\y%n6P Po48,-/#òxE(w6FNp$a@3 = @3/~3??E5 B2M 8Acр鄪p\8<""&&&@QTU;Zsǎ>rر#Я R[;ATG>_D<>6ly[La 4:hF %4.\dr'|\(V' ٹ99z4AHrq1{R X ׮_n`<O&15=`0M8^8n?(kfwk(vK.~7={V`YIe[13z<Ԩr4ƅ 䉉W;W^o4MZ-m\v ^$I`{{{{{r酕Gݽ> ?1 VU  A !>rCˉ@,CVWo}k?.KK'u]_g[N3=D"ƇÑV @Z֠$]A [J.*YAh x^gYOvI>VRv-vI{=4MPG͛PUu``xz111s<fknn_BA\F0Dn.u`;  f<zb?uZo [A`oo/]^ IDATD­bd 0 |*"TUE,r]\ u:DxEQd2 YqmAWR(B2;J6 E,hV*uY>8tJz~~=,߻pr牍Ͱ 66LUԿ*r²,x<MJ_WmZӴ3 ¡r9ߞp8Ӹu֠mY4P~䜢(p\5 ,BQTq/?P(FD"˅VVYQt!V("@UUN`O>y.l [A.<VF( |$ i -;vV$D"I4W0EA^GRizV Ih\KKl ?(T/W*gQH$nWFV;pc[[[͛7ooT*Oͨall( ˏaUՖMӄ8w]ܺu ǃWrAI.ӴsW{Vy[^^O?(Jnp8` @4のPTUR6m+=JD vLD$Aٸh>^74aU^^>"brAUU4M,--!͢jjod2{gDQz.4]渀DJÇcyy7oބzp:0 wI@$zENH1 X\*UXoaaa]T/r:`07og>-IVRTu֭4vvvJKJB&$IAXYY޶afAz0{9?WWF\G À$Ihېe\$I&C _G3}E8tP(]f؅L&Kʫ$  G4 0 "<H$˵~~~Q?Z|2LLD"vl6///# EQwCBD0$Is'|d5=YA`gài7Cw8h4`fP'go4g&dYNWLDX?6AI[JLf͛^|G?("☢(t ˲0Z@9EQ`f߄J,zƵ( o&`&HD. y]hetp8=6dIk 3xd2nKz=qyľacxPiUUjh׆a˲vFPדӓ|Tb3|FndYh67$ 4/?6l666oicP.O> UUT*烮ȩb!IҠtax< nO4}PVE #t:dB*s'[gݶ p^##! b&^ֻͮC0w}X  4M\t `03lA,..'? qDz=躎h4:؝ O&oo5 ˲3lX]]|sp[\bh>ŋ?vieq\fh۠nX^^߇0i @R  `,19lyt:-|]޾~n`Jey$ 7onE%J8eH$x0==b8PAvH$@O?t:ollb*ke8TuS)@4iV4XbQhyHr[x0Aˢ(n(B!$G" 0 bvMa ^2 #R^/EdaT zBuA(Z׋z9pXWzA*4MCl5_=`Z~^o PRe,cqD2ukDr;l}7oF{7E9SEE ]etz[j Ahrk;q"u_9.@$A @VÇj2 %yH/~L7p8$ z!ant߱$ *TU| ÈjYXUU 0>>NV^7hdYVRMӠ:TU{]Ud&7fLT*hZ0 DSSSPUNN'2qq:i\v x >V W^E [cJ9NP5x04 a ˥666g?Ou⬦i4c1:p8 :9B^S2,BTO?9\.|S?~?@r@$`E,fsÖPN_y`. GawvCeq|>Xd2Ǐ# ²,oԺ)<'Lrї58G{On.2BCH6[8yo6߮jze:A@.CZE4, TU8f+Fp8Gy{{{xwnnzrJ^`^n͛7r!<{e2̰D"HE?Fop8NoeRH$tώ7 h6u iwT#2Lfkz'=/bOӐ$ Php LNNaS'N$SK"Cy躎˗/c/r0MJ FXza{,ؐeeYHEQ I?ԃh7ЅD"4 Á=t]4VRV5i^oMUKD܃<Ϭ~z~3Bb$mll)|%n-( IEvcaa,FYEVIXXX0F, juQ.cNv @e躎p8E<#CKλ̳?%7}^~bqQ~\~p%Y=c=ix<0?(rGM ].!28C\izLfwMʊ|> >oFVC.J//-:Ai UU@w5b!BQPh4 $!Y<~h;A1^cvvhha^Yvo~s;z,,}7Zd2}?q;zIr?j:^>s?t|BCN&K.×˥o(zC 0urX F;;;4 h4=7M}*}e3on \|^VT7cMǎIVK০&Q!"( Y4M`jj {{{4 aa{hefhHƍy&n7ti!g( @& Hґ&I[n߅/*DQh㸓ZזWqq~[׿/jSӆapv۩q`cc3 O7o> l24M]z)Zz%ܹsT*aχZj Ӊj|3e/^ƄBbic|J%,,,`ccP\ši]ӑp8M: EfmR;{'S׋v$kgΜy( 666Ngc $N Τd2W*¡CɖL8?fR||qq KQ,.ju鴦imt:~uA_ƍa(Ji$ ,CQhca -˂ix1>EAeP5zaaFcnںuر#ٿuvvvz1;;RׯSSl̽mll6vt:N3~#֍w{|"˲6\.7hw~݋N3w׋v Ai(Cр$T*w̌;1re BQ*pa,..q,s^Z4m4gy$AUUh=H^94=ñ4MT*韮~z)K_,R@Eaܸqe3RޝiZ @"H@.\[$OvzyyT6ş * 28fT*p80 Nb. ׮]CF( E. @~jJ4M#8PF)looCUջ#p8ˇncc`=llll~T0p\bzu4MaK z4zhtd /ST2ˡ@UUe H92Z*ׯodYvC$;Sccnl>z( ѥ4M3 e%A@ZE^G@0D0nܸn;lh2 `lEFO<ݽpBAj* JH,:He{{{F?7gwHq|> Z-t]lmmAu{h}SQLMMnҥKp\h`Ns`؏݌bPtFt:xPC#`l<a1 =lEgfD 13=GA<lBU{ub ǁ`Y^>'? 9QaYt]h7h4p˵;DD$A8TUE>)g.pN ($ .\H{߻ˍ_E?˲W^ Bv^ E8R~?]סiX0 cnӂ ,JuUea&gvȋ`Wt:<c666l<'+ Xq+r7&M<|s~nV$X623l}>޹=H$EMrŞ0|tt:$Z(~x<E^t:U,P˲D"T*E߬7|nbbՉ ܸq3338v677x Iց&O=TX,l& À0M|޽ƃrQi`YH$I !,;W_}u5UUUU=j`&"~?&''Ar4M0 w8DQD6E8B$<O8qώ' Cx4 1== M0>>ؿ\j{C$M*i\.\. 7Rf$.B ^UX˱cϰ B moocoouF: ZUtym(%{~rjo/bYX f<ϣhraffZ a  XϯԧyOz*I8i*0 n7=]#vZMHb1@48<DQkkk+?ikfguMB]IpUUaYr$I˲`YW\A&.&''F/,,qgqoOo}:%666PFIf0 3 ` _-B^~(mA@^GRt6,=ȼD"7CQ ۍjh4X,LFlm¡0,˲8t 2X}FTt]?399yJE(b8؅ BvR~vN) ;(8 W#E\|_{R$F/ 7o~;F(L&}Zr|. ʕ+~:0$I|ahd2Z($Ir`6RTxkk+_r*S`3 - R3)IiZeL8 ިכ;7~틶-؅!Ib Տyd2D"p{Lr!lnn,pA011CGfAA4 ±x{'gHްex"pt:v!I0 Chg|>4TEEQin+++ 77Nߏ+VWWeYLQt:{ $I"~:X$ID"C՛L{) IDAT\kSSSq|> PE~'Z|Yxw@QԠò,(Q_ݽG<#I&|SSŇz'A[[[w'{tCt:`~ H  …tf |t:鄮h6hZ(J-GA^! ayy~o9Zy?3Pߏcǎabb^tllld+W^ Izx뭷P( 4{zqX[{%InSSS}6@ %p aj`A4^/HG㇩q@`p{dYi +Kyğ)/{?H'NHu:)Ñr\FcE 2~?t],+l0 #C$677!2^/t] 666=vG͇9C<>|Ktb2\t:iZBPXz,[FQT*emc80 m@^!t{L&UE=e|>r9Le2ں?!s=H,4MӪ+@ f{ymm<|ӥH$ .E`0.zލVK[twZC @$A8 oVyF(Fuz~?!4M VRh4՛r8^>G$A\Fلym: ,9MEL0A0`YZ({-NOOO~{~RTPV!"BDQp8 0M<@ X,R^Tn{=]GBhQf1a?7 SSSj5t] 'r/\*Ba`YB(eYHX,Ƨ3VBJ,H߲m$Ib<#gE:8~?^}ľ%D"X,UUyn޼9$j&lۆz7~'+Qܹss:jz7]ya:sUp\.~` q8,}3㝎w~Rzvm-\H&.j?mDŎ"OO}jiٲ >Z ò,{/{6Οܥ=8Q[2b-O9;ʲH$\.t: uq\2XYZz8;ysccrm_~IQTV;~3]ݸS~af;s汵OSUa`4?2gVU~ٞ~TߧN*~H$IPa&~uQ+,LJ!J I4z~at:ݵl6N߀BEz=(d*5 "L,qaAEP(hĉ߇y$% I$a~~@h&d/Ajt$F m#ˡjA$qF#uvdWUZݿ _'<Eg4,p8P0s0@OǃĽbCa Lbkk ܄ , l"dYv=npqz=j?ٌyl+n YaZ NJ'Aa&7)ҭ[ƭ/B‹7on\tNxs-^٫#?eYuzPrnyz*Ic=y0 DV B@@333hxqqqN͝*cP :N?y:0TUiPib:?^{Aem_ЦFVK5 >T xF رc8{l1~̙%5Tx\bq]w4ӆG 6,B _ h6H$XXXx?ՙi IA,C^4 ƍ[cKUM($gO4 q{ N8]ksAjA0MHZ q^zh4+e<{/:/0<~B[k/;^;u"2By6DD g4}|Ѳaܸq^>Ǐ4ۻ/Jy9ܹbi^e^ޓe[Wnm.M:?st]4+]0 |"t]G$eYVe|zC9-fKǎ}Wl_ݑ;i5M<#T*NCo{a|_(TU\zēO>9*d2FX\\v[\^}g8ܗJV+r98qbooFc|c0`jj ;;;HRh4Z޺uk}veƍnnn"! u]\ڑ9T,mU]d``=| A69(T* dYZ>dP(4b8N(X,ƃyzz:jh40;;x~?.xVb* TUEXo4NEIF!I N#L*J!UbN3EVw t]|Παc?_g^k f_.\(e~~kk lv<%7^v PUl 0yA(p]eg6TZ֪뚫o^AJŸ3 wwwjǑ`Y$I?ra78"":gΜ)Zಪ󊢠  ]E_K <_V1['b~?W*ҤsB!wEEQéS:n߾\.󰳳h4 χSNVZ`F6i]á5۶L&Jl]{0`&v~ EQ0==wy{o^ NkR).eh#`8z)6.. 6 xG,wb޾6uX,H$0i"8{=áS^OZSGƔ~4H?oM~(R).Eb0R(.z7vֳ^C$}<#?x|>"bx;S x?~֭[<kHDȓ@DD{キp\(jA[GdViqH0]T'_)5 . Zm5 $ 󘟟G߇ax7*EA߇۷tP,yvwJxmAvp8(Y1;;wBe DQ9s*6EQpmB!*Aiڤݯ(=\,;BwAD"Ų DQD*BZEC @<IyxB"6t]G"`N3^Fa&B8>Ͻ[?SWa''wBHQ]>z_-..DQAO`0@:T׭;C0X.K^h4J~}\@D kt:H ]ourTG~Y\.( \j(>jEpo(<BDQիWq),,,h Hm##`p$a8qlmm[{=#R&SضR?0_|P?}ϬCh4z>H^! 7  H&:a0777~>\twv=^( ƿFJXY_w7=Xmj6R$"vvvl.]}K=_:<G˪܄0 qxNG+L:#у­DDtdZZ5'TU}*" h,Ds,߼ys5J_FeY. ۶8Z ,˂iSNqxUU/Оa8l_EA0 mMkcjj ^5WܹiG~Vugwwuvf29O8(nƸu133Y 1a4رcui"Ju]W|yeu<7n=Sʿ-Ki lۆXXX۷y&07WzL&Qa6 <ϻ}#ts=,4ёtNd2ǃ`4`0xq8t( @(oooNO_E!8H$Z-8~?^!yA(A`Ni!g۶a64!2>\EZE:mې$ ssszCG8H q/~s/{+l6j~?hvzK:<'뽕4`ww/4-~ AMHXk|;wl&$IB*yharqWD݆y$ hxG I666^?ff2EQ]e>8ۃ rBZyr+&3@fY ÃΐzAڏ~LN-D0 IiP޵9c쩧*Xq7" :* \E  H_""X78""#X,kݒ$slQijy\ׅaHD.C$MӠ*b޽T*1ph#?,..JFEMkq4 CUUp8\.Fqpvק oCw9h4t*r??xdVؙ҃Gh4ZO=8L[oT*nlvG{VBO66n]׍G"am;g,}tNS[[Z,_eˊOΟ?pXśoUsvvʙLl&T\?ծO/l{j(BӁ(OOC4F#+ۥh/zV?ŐJtQVL&yƥI$":l,4ŅK iLh4HXXXvT>hGAVCWQa6~?>FIg$"zXh """:DVcEUU$It:aZ-DQ ?A9? WEPǎa8lٳhZpݻY"_W'Q0ѩ)xBX,j _tFz8_Xeb0`8jӥ¤s6v4/| +dfl6X,JYyqy9q]h4bAxit]'?A:=U6DD"""CRy XXX@4H&hZpFΕrZtNzx‘OF4 r|><ϋO:#ac/W$ $ leb8ެ0pٶH$Qxv IDAT"C{Ig$":l,4}HO=Tagg뚦uw6:Ci}_՚6 c#m0p xpqDN!Ig$":lҤ}eW~*$QN487-s''GV'O4ͯj5q  χ`0ߏT*iYi2ˁ@~il4 DDDDW|>=UUq9Hv9躎``!Q^t^qx%r9 (<f0لDDW'~OO\uw ']h%dU ziqi}Q-Ν;0Mv¶mhUkHDt8 03[ *;۷ockk ,ömӉ?'&kitN \\,eM1DD"""ї ŐfffJq 2dY~?+L7 kAT*]m0Woݺ2DD"""Å  pU4ETU~u mwY֤sѠiy+>*˾ ,|YQjUjK"X0H"""ԩ76nލD"Fq8H$$ `~~t'_k}m yϟvzcGt"""OFyIp8i m(EQ !,O2 lF (D"޽^yH۷oԩEtoO^8裌""":bi|aah JAxfVyhZ{mz:fVy8t?O߳P(Hnaj*[DVpY ylFA:O<~md2CEX,P(t:`d2znPVqIt]4  Q( SWnY.9̏q%I=v$IR¶m`0X,pl6 ۶ pk:(A@"@$A. , EQP 2& À8d2zmssshۨ7B2k?/ƵDDD""": F~1`0 c4;nݺQd Y.<σi.<H$P~X A J"bDIB݂  !ôLHh,|ѻoV}g?{o}χbrRɊ8˩T ,qض'NPU׮]C#<EQ0 y|>&2 {<PiBET*t]d2qt]ò,t:``|{*>koRaV>;;}%WUU߇iBF:aPU^hlCUUlmma4!AE8˲Ndy\_u` ^UoW_/@DD;0H"""ȸp +~0R2ٶۈbp8ӧ~0 #ׁiP¶me (fy5J V_u^ ""Xh ""',%T:v,\lN81yydYJ`|>) ůۭֆCcm}zyG DDDQ*}hv|8%I.yt]hd2 YVL\.]/' ]IENDB`# PocketMine-MP 主配置文件 # 此处包含 server.properties 中无法包含的设置 # 某些设置的修改是安全的,而某些设置修改后将导致服务器无法工作 # 升级服务器后,新的设置或默认值将不会自动在此处显示 settings: #服务器端本地化翻译所使用语言,由三个字母组成的代码表示 #可在 https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes 查询语言代码 language: "chs" #是否将由服务器端翻译的上面语言的提示信息发送到客户端,还是由客户端根据玩家选择的语言翻译 force-language: false #当服务器关闭时,下列提示信息将在玩家客户端上显示。 shutdown-message: "服务器已关闭" #允许通过 Query 查询插件列表 query-plugins: true #当有插件使用已弃用的 API 方法时,在控制台显示信息 deprecated-verbose: true #默认启用插件和核心性能分析 enable-profiling: false #仅在测量的游戏刻低于或等于设定值时记录结果(默认为 20) profile-report-trigger: 20 #异步任务的线程数。 #用于插件异步任务、世界生成、压缩和网络传输。 #将此值设置为大致与您 CPU 的核数相同。 #当设置为 auto 时,将会尝试自动检测 CPU 核数(或自动设为 2) async-workers: auto #允许使用非出产的开发版本。警告:除非您知道自己在做什么,否则请勿启用此选项 enable-testing: false memory: #全局非严格内存限制,以 MB 为单位。设置为 0 可禁用 #当全局内存占用超过此限制时,将会激活低可用内存触发器并释放内存 global-limit: 0 #主线程非严格内存限制,以 MB 为单位。设置为 0 可禁用 #当主线程内存占用超过此限制时,将会激活低可用内存触发器并释放内存 main-limit: 0 #主线程严格内存限制,以 MB 为单位。设置为 0 可禁用 #当主线程内存占用超过此限制时,将会停止服务器 main-hard-limit: 1024 #检查内存使用的周期,以游戏刻为单位(默认为 1 秒) check-rate: 20 #当内存不足时继续执行低可用内存触发器 continuous-trigger: true #当 memory.continuous-trigger 选项已启用时,指定 memory.check-rate 步骤的速率(默认为 30 秒) continuous-trigger-rate: 30 garbage-collection: #垃圾回收的时间间隔(默认为 30 分钟),以游戏刻为单位,设置为 0 可禁用 #此设置只影响主线程。其它线程应各自分别进行垃圾回收 period: 36000 #执行异步任务,以从各线程回收垃圾 collect-async-worker: true #当内存不足时触发 low-memory-trigger: true max-chunks: #当内存不足时每个玩家的最大渲染视距 chunk-radius: 4 #触发区块垃圾回收 trigger-chunk-collect: true #当内存不足时触发 low-memory-trigger: true world-caches: disable-chunk-cache: true low-memory-trigger: true network: #批量处理数据包的阈值,以字节为单位。只有大小大于该阈值的数据包会被压缩 #设置为 0 可压缩所有数据包,设置为 -1 可禁用。 batch-threshold: 256 #批量处理数据包时的压缩级别。更高级别 = 更多 CPU 占用,更低级别 = 更多带宽占用 compression-level: 2 #使用异步任务进行压缩。将会导致额外 0.5~1 刻的延迟,降低主线程的 CPU 占用 async-compression: true #实验性功能,仅适用于 Windows。尝试使用 UPnP 进行自动端口转发 upnp-forwarding: false debug: #若要启用断言执行,请在 php.ini 中将 zend.assertions 设为 1 assertions: #当 php.ini 中启用断言时发出警告,因为断言可能会影响运行时性能。 warn-if-enabled: true #当断言失败时抛出异常,可允许收集与断言失败相关的更详细的信息,但可能导致服务器崩溃。 #当设置为 false 时,将不会抛出异常,而是显示警告信息。 throw-exception: false #当设置的值大于 1 时,将会在控制台显示调试信息 level: 1 #启用 /status、/gc 命令 commands: true player: #选择是否保存玩家数据。 save-player-data: true anti-cheat: #当设置为 false 时,将会尝试阻止疾跑和穿墙作弊。可能导致与某些尚未适配的方块有关的问题。 allow-movement-cheats: true #当设置为 false 时,将会检查玩家破坏方块的时间,阻止相关作弊。可能导致与某些尚未适配的方块有关的问题。 allow-instabreak: false level-settings: #创建地图时使用的默认格式 default-format: pmanvil #自动更改地图的刷新速度,以维持 20 游戏刻每秒 auto-tick-rate: false auto-tick-rate-limit: 20 #设定基础刷新速度(1 = 20 游戏刻每秒,2 = 10 游戏刻每秒) base-tick-rate: 1 #每游戏刻都刷新所有玩家,即使其它设置不允许这样做。 always-tick-players: false chunk-sending: #若要更改服务器正常渲染视距,请在 server.properties 中修改 view-distance。 #每游戏刻发送给玩家的区块数量 per-tick: 2 #需要在玩家出生前发送的区块半径 spawn-radius: 4 #为提高区块发送速度,在内存中保存一份区块的序列副本 #适合在几乎静止、但在同一时间有许多玩家加入的世界使用 cache-chunks: true #使用异步任务排序要发送的区块。 async-chunk-request: false chunk-ticking: #每游戏刻处理的区块数量上限 per-tick: 16 #玩家周围随游戏刻更新的区块半径 tick-radius: 3 light-updates: false clear-tick-list: true #禁用随机刻的方块 ID disable-block-ticking: #- 2 # grass chunk-generation: #等待生成的区块队列的最大长度 queue-size: 2 #等待填充的区块队列的最大长度 population-queue-size: 2 ticks-per: animal-spawns: 400 monster-spawns: 1 autosave: 6000 cache-cleanup: 900 spawn-limits: monsters: 70 animals: 15 water-animals: 5 ambient: 15 auto-report: #发送错误报告 enabled: false send-code: true send-settings: true send-phpinfo: false host: crash.pocketmine.net anonymous-statistics: #发送用于数据收集和插件使用情况统计的匿名数据 enabled: true host: stats.pocketmine.net commands: #您可以在此处自定义服务器的指令 #在此处指定指令的名称,以覆盖默认值。 #如果没有为某个指令设定自定义值,将会为该指令应用默认值。 #注意:部分指令无法在此被禁用,例如 /stop、/reload 等重要指令。 default: true #在此处禁用指令 #例如,删除下面注释前的井号将会禁用 /version 和 /plugins 指令 #version: false #plugins: false timings: #选择查看您的计时数据的主机。 host: timings.pmmp.io aliases: #示例: #showtheversion: version #savestop: [save-all, stop] worlds: #这些设置将会覆盖 server.properties 中的生成器设置,并允许加载多个世界。 #示例: #world: # seed: 404 # generator: FLAT:2;7,59x1,3x3,2;1;decoration(treecount=80 grasscount=45) # Main configuration file for PocketMine-MP # These settings are the ones that cannot be included in server.properties # Some of these settings are safe, others can break your server if modified incorrectly # New settings/defaults won't appear automatically on this file when upgrading. settings: #Three-letter language code for server-side localization #Check your language code on https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes language: "eng" #Whether to send all strings translated to server locale or let the device handle them force-language: false #When server shut down, the players will get kicked and this is what will show on there screen. shutdown-message: "Server closed" #Allow listing plugins via Query query-plugins: true #Show a console message when a plugin uses deprecated API methods deprecated-verbose: true #Enable plugin and core profiling by default enable-profiling: false #Will only add results when tick measurement is below or equal to given value (default 20) profile-report-trigger: 20 #Number of AsyncTask workers. #Used for plugin asynchronous tasks, world generation, compression and web communication. #Set this approximately to your number of cores. #If set to auto, it'll try to detect the number of cores (or use 2) async-workers: auto #Enables use of non-production, development builds. WARNING: DO NOT enable this unless you are sure you know what you are doing. enable-testing: false memory: #Global soft memory limit in megabytes. Set to 0 to disable #This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this global-limit: 0 #Main thread soft memory limit in megabytes. Set to 0 to disable #This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this main-limit: 0 #Main thread hard memory limit in megabytes. Set to 0 to disable #This will stop the server when the limit is surpassed main-hard-limit: 1024 #Period in ticks to check memory (default 1 second) check-rate: 20 #Continue firing low-memory-triggers and event while on low memory continuous-trigger: true #Only if memory.continuous-trigger is enabled. Specifies the rate in memory.check-rate steps (default 30 seconds) continuous-trigger-rate: 30 garbage-collection: #Period in ticks to fire the garbage collector manually (default 30 minutes), set to 0 to disable #This only affect the main thread. Other threads should fire their own collections period: 36000 #Fire asynchronous tasks to collect garbage from workers collect-async-worker: true #Trigger on low memory low-memory-trigger: true max-chunks: #Maximum render distance per player when low memory is triggered chunk-radius: 4 #Do chunk garbage collection on trigger trigger-chunk-collect: true #Trigger on low memory low-memory-trigger: true world-caches: disable-chunk-cache: true low-memory-trigger: true network: #Threshold for batching packets, in bytes. Only these packets will be compressed #Set to 0 to compress everything, -1 to disable. batch-threshold: 256 #Compression level used when sending batched packets. Higher = Uses More CPU, Less = More Bandwidth Usage compression-level: 2 #Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread async-compression: true #Experimental, only for Windows. Tries to use UPnP to automatically port forward upnp-forwarding: false debug: #To enable assertion execution, set zend.assertions in your php.ini to 1 assertions: #Warn if assertions are enabled in php.ini, due to assertions may impact on runtime performance if enabled. warn-if-enabled: true #Enable throwing exceptions when assertions fail, will allow obtaining more detailed information on the failed assertion, but may cause a server crash. #If set to false, a warning will be raised instead of throwing an exception. throw-exception: false #If > 1, it will show debug messages in the console level: 1 #Enables /status, /gc commands: true player: #Choose whether to enable player data saving. save-player-data: true anti-cheat: #If false, will try to prevent speed and noclip cheats. May cause movement issues with some blocks which are not yet properly implemented. allow-movement-cheats: true #If false, times block breaks to ensure players are not cheating. May cause issues with some blocks which are not yet properly implemented. allow-instabreak: false level-settings: #The default format that levels will use when created default-format: pmanvil #Automatically change levels tick rate to maintain 20 ticks per second auto-tick-rate: false auto-tick-rate-limit: 20 #Sets the base tick rate (1 = 20 ticks per second, 2 = 10 ticks per second, etc.) base-tick-rate: 1 #Tick all players each tick even when other settings disallow this. always-tick-players: false chunk-sending: #To change server normal render distance, change view-distance in server.properties. #Amount of chunks sent to players per tick per-tick: 2 #Radius of chunks that need to be sent before spawning the player spawn-radius: 4 #Save a serialized copy of the chunk in memory for faster sending #Useful in mostly-static worlds where lots of players join at the same time cache-chunks: true #Use AsyncTasks for serializing chunks for sending. async-chunk-request: false chunk-ticking: #Max amount of chunks processed each tick per-tick: 16 #Radius of chunks around a player to tick tick-radius: 3 light-updates: false clear-tick-list: true #IDs of blocks not to perform random ticking on. disable-block-ticking: #- 2 # grass chunk-generation: #Max. amount of chunks in the waiting queue to be generated queue-size: 2 #Max. amount of chunks in the waiting queue to be populated population-queue-size: 2 ticks-per: animal-spawns: 400 monster-spawns: 1 autosave: 6000 cache-cleanup: 900 spawn-limits: monsters: 70 animals: 15 water-animals: 5 ambient: 15 auto-report: #Send crash reports for processing enabled: false send-code: true send-settings: true send-phpinfo: false host: crash.pocketmine.net anonymous-statistics: #Sends anonymous statistics for data aggregation, plugin usage tracking enabled: true host: stats.pocketmine.net commands: #Here you can customize server commands #Specify command names to override the default set here. #If no custom value is defined for a command, the default will be used. #NOTE: Some commands cannot be disabled here, such as the important ones like /stop, /reload, etc. default: true #Set override values per command here #For example, uncommenting the below will disable /plugins and /version #version: false #plugins: false timings: #Choose the host to use for viewing your timings results. host: timings.pmmp.io aliases: #Examples: #showtheversion: version #savestop: [save-all, stop] worlds: #These settings will override the generator set in server.properties and allows loading multiple levels #Example: #world: # seed: 404 # generator: FLAT:2;7,59x1,3x3,2;1;decoration(treecount=80 grasscount=45) # PocketMine-MPの詳細設定ファイルです。 # これらはserver.propertiesに組み込めなかったものをここにまとめました。 # これらの設定の一部は安全です。間違って変更された場合は、サーバーを壊す可能性があります # アップグレード時に新しい設定/デフォルト値がこのファイルに自動的に表示されることはありません。 settings: #三文字の言語コード #https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes から調べることができます language: "jpn" #サーバーのロケールに変換されたすべての文字列を送信するか、デバイスがそれらを処理するかどうか force-language: false #サーバーのクローズ時に送るメッセージ shutdown-message: "サーバーは閉じられました" #クエリ経由でプラグインのリスト表示を許可する query-plugins: true #プラグインが非推奨のAPIメソッドを使用しているときにコンソールメッセージを表示する deprecated-verbose: true #プラグインとコアプロファイリングをデフォルトで有効にする enable-profiling: false #Tick測定値が指定された値以下の場合にのみ結果を追加します(デフォルト20) profile-report-trigger: 20 #AsyncTask workerの数 #プラグインの非同期タスク、ワールド生成、圧縮、Web通信に使用されます。 #これをあなたのコア数に合わせてください。 #autoに設定すると、コアの数を検出しようとします(または2を使用します) async-workers: auto #開発ビルドの使用を可能にします。 警告:あなたが何をしているのか分からない限り、これを有効にしないでください。 enable-testing: false memory: #Global soft memory limit in megabytes. Set to 0 to disable #This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this global-limit: 0 #Main thread soft memory limit in megabytes. Set to 0 to disable #This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this main-limit: 0 #Main thread hard memory limit in megabytes. Set to 0 to disable #This will stop the server when the limit is surpassed main-hard-limit: 1024 #Period in ticks to check memory (default 1 second) check-rate: 20 #Continue firing low-memory-triggers and event while on low memory continuous-trigger: true #Only if memory.continuous-trigger is enabled. Specifies the rate in memory.check-rate steps (default 30 seconds) continuous-trigger-rate: 30 garbage-collection: #Period in ticks to fire the garbage collector manually (default 30 minutes), set to 0 to disable #This only affect the main thread. Other threads should fire their own collections period: 36000 #Fire asynchronous tasks to collect garbage from workers collect-async-worker: true #Trigger on low memory low-memory-trigger: true max-chunks: #Maximum render distance per player when low memory is triggered chunk-radius: 4 #Do chunk garbage collection on trigger trigger-chunk-collect: true #Trigger on low memory low-memory-trigger: true world-caches: disable-chunk-cache: true low-memory-trigger: true network: #Threshold for batching packets, in bytes. Only these packets will be compressed #Set to 0 to compress everything, -1 to disable. batch-threshold: 256 #Compression level used when sending batched packets. Higher = Uses More CPU, Less = More Bandwidth Usage compression-level: 2 #Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread async-compression: true #Experimental, only for Windows. Tries to use UPnP to automatically port forward upnp-forwarding: false debug: #To enable assertion execution, set zend.assertions in your php.ini to 1 assertions: #Warn if assertions are enabled in php.ini, due to assertions may impact on runtime performance if enabled. warn-if-enabled: true #Enable throwing exceptions when assertions fail, will allow obtaining more detailed information on the failed assertion, but may cause a server crash. #If set to false, a warning will be raised instead of throwing an exception. throw-exception: false #If > 1, it will show debug messages in the console level: 1 #Enables /status, /gc commands: true player: #Choose whether to enable player data saving. save-player-data: true anti-cheat: #If false, will try to prevent speed and noclip cheats. May cause movement issues with some blocks which are not yet properly implemented. allow-movement-cheats: true #If false, times block breaks to ensure players are not cheating. May cause issues with some blocks which are not yet properly implemented. allow-instabreak: false level-settings: #The default format that levels will use when created default-format: pmanvil #Automatically change levels tick rate to maintain 20 ticks per second auto-tick-rate: false auto-tick-rate-limit: 20 #Sets the base tick rate (1 = 20 ticks per second, 2 = 10 ticks per second, etc.) base-tick-rate: 1 #Tick all players each tick even when other settings disallow this. always-tick-players: false chunk-sending: #To change server normal render distance, change view-distance in server.properties. #Amount of chunks sent to players per tick per-tick: 2 #Radius of chunks that need to be sent before spawning the player spawn-radius: 4 #Save a serialized copy of the chunk in memory for faster sending #Useful in mostly-static worlds where lots of players join at the same time cache-chunks: true #Use AsyncTasks for serializing chunks for sending. async-chunk-request: false chunk-ticking: #Max amount of chunks processed each tick per-tick: 16 #Radius of chunks around a player to tick tick-radius: 3 light-updates: false clear-tick-list: true #IDs of blocks not to perform random ticking on. disable-block-ticking: #- 2 # grass chunk-generation: #Max. amount of chunks in the waiting queue to be generated queue-size: 2 #Max. amount of chunks in the waiting queue to be populated population-queue-size: 2 ticks-per: animal-spawns: 400 monster-spawns: 1 autosave: 6000 cache-cleanup: 900 spawn-limits: monsters: 70 animals: 15 water-animals: 5 ambient: 15 auto-report: #Send crash reports for processing enabled: false send-code: true send-settings: true send-phpinfo: false host: crash.pocketmine.net anonymous-statistics: #Sends anonymous statistics for data aggregation, plugin usage tracking enabled: true host: stats.pocketmine.net commands: #Here you can customize server commands #Specify command names to override the default set here. #If no custom value is defined for a command, the default will be used. #NOTE: Some commands cannot be disabled here, such as the important ones like /stop, /reload, etc. default: true #Set override values per command here #For example, uncommenting the below will disable /plugins and /version #version: false #plugins: false timings: #Choose the host to use for viewing your timings results. host: timings.pmmp.io aliases: #Examples: #showtheversion: version #savestop: [save-all, stop] worlds: #These settings will override the generator set in server.properties and allows loading multiple levels #Example: #world: # seed: 404 # generator: FLAT:2;7,59x1,3x3,2;1;decoration(treecount=80 grasscount=45) # PocketMine-MP 主配置檔案 # 此處包含 server.properties 中無法包含的設定 # 某些設定的修改是安全的,而某些設定修改後將導致伺服器無法工作 # 升級伺服器後,新的設定或預設值將不會自動在此處顯示 settings: #伺服器端本土化翻譯所使用語言,由三個字母組成的代碼表示 #可在 https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes 查詢語言代碼 language: "zho" #是否將由伺服器端翻譯的上面語言的提示訊息發送到客戶端,還是由客戶端根據玩家選擇的語言翻譯 force-language: false #當伺服器關閉時,下列提示訊息將在玩家客戶端上顯示。 shutdown-message: "伺服器已關閉" #允許通過 Query 查詢插件列表 query-plugins: true #當有插件使用已棄用的 API 方法時,在控制台顯示訊息 deprecated-verbose: true #預設啟用插件和核心性能分析 enable-profiling: false #僅在測量的遊戲刻低於或等於設定值時記錄結果(預設為 20) profile-report-trigger: 20 #異步處理的線程數。 #用於插件異步處理、世界生成、壓縮和網絡傳輸。 #將此值設定為大致與您 CPU 的核數相同。 #當設定為 auto 時,將會嘗試自動檢測 CPU 核數(或自動設為 2) async-workers: auto #允許使用非出產的開發版本。警告:除非您知道自己在做什麼,否則請勿啟用此選項 enable-testing: false memory: #全域非嚴格記憶體限制,以 MB 為單位。設定為 0 可停用 #當全域記憶體占用超過此限制時,將會激活低可用記憶體觸發器並釋放記憶體 global-limit: 0 #主線程非嚴格記憶體限制,以 MB 為單位。設定為 0 可停用 #當主線程記憶體占用超過此限制時,將會激活低可用記憶體觸發器並釋放記憶體 main-limit: 0 #主線程嚴格記憶體限制,以 MB 為單位。設定為 0 可停用 #當主線程記憶體占用超過此限制時,將會停止伺服器 main-hard-limit: 1024 #檢查記憶體使用的周期,以遊戲刻為單位(預設為 1 秒) check-rate: 20 #當記憶體不足時繼續執行低可用記憶體觸發器 continuous-trigger: true #當 memory.continuous-trigger 選項已啟用時,指定 memory.check-rate 步驟的速率(預設為 30 秒) continuous-trigger-rate: 30 garbage-collection: #垃圾回收的時間間隔(預設為 30 分鐘),以遊戲刻為單位,設定為 0 可停用 #此設定只影響主線程。其它線程應各自分別進行垃圾回收 period: 36000 #執行異步處理,以從各線程回收垃圾 collect-async-worker: true #當記憶體不足時觸發 low-memory-trigger: true max-chunks: #當記憶體不足時每個玩家的最大渲染視距 chunk-radius: 4 #觸發區塊垃圾回收 trigger-chunk-collect: true #當記憶體不足時觸發 low-memory-trigger: true world-caches: disable-chunk-cache: true low-memory-trigger: true network: #批次處理數據包的閾值,以字節為單位。只有大小大於該閾值的數據包會被壓縮 #設定為 0 可壓縮所有數據包,設定為 -1 可停用。 batch-threshold: 256 #批次處理數據包時的壓縮級別。更高級別 = 更多 CPU 占用,更低級別 = 更多流量占用 compression-level: 2 #使用異步處理進行壓縮。將會導致額外 0.5~1 刻的延遲,降低主線程的 CPU 占用 async-compression: true #實驗性功能,僅適用於 Windows。嘗試使用 UPnP 進行自動端口轉發 upnp-forwarding: false debug: #若要啟用斷言執行,請在 php.ini 中將 zend.assertions 設為 1 assertions: #當 php.ini 中啟用斷言時發出警告,因為斷言可能會影響運行時性能。 warn-if-enabled: true #當斷言失敗時拋出異常,可允許收集與斷言失敗相關的更詳細的訊息,但可能導致伺服器無回應。 #當設定為 false 時,將不會拋出異常,而是顯示警告訊息。 throw-exception: false #當設定的值大於 1 時,將會在控制台顯示調試訊息 level: 1 #啟用 /status、/gc 指令 commands: true player: #選擇是否儲存玩家數據。 save-player-data: true anti-cheat: #當設定為 false 時,將會嘗試阻止疾跑和穿墻作弊。可能導致與某些尚未適配的方塊有關的問題。 allow-movement-cheats: true #當設定為 false 時,將會檢查玩家破壞方塊的時間,阻止相關作弊。可能導致與某些尚未適配的方塊有關的問題。 allow-instabreak: false level-settings: #創建地圖時使用的預設格式 default-format: pmanvil #自動更改地圖的刷新速度,以維持 20 遊戲刻每秒 auto-tick-rate: false auto-tick-rate-limit: 20 #設定基礎刷新速度(1 = 20 遊戲刻每秒,2 = 10 遊戲刻每秒) base-tick-rate: 1 #每遊戲刻都刷新所有玩家,即使其它設定不允許這樣做。 always-tick-players: false chunk-sending: #若要更改伺服器正常渲染視距,請在 server.properties 中修改 view-distance。 #每遊戲刻發送給玩家的區塊數量 per-tick: 2 #需要在玩家出生前發送的區塊半徑 spawn-radius: 4 #為提高區塊發送速度,在記憶體中儲存一份區塊的序列副本 #適合在幾乎靜止、但在同一時間有許多玩家加入的世界使用 cache-chunks: true #使用異步處理排序要發送的區塊。 async-chunk-request: false chunk-ticking: #每遊戲刻處理的區塊數量上限 per-tick: 16 #玩家周圍隨遊戲刻更新的區塊半徑 tick-radius: 3 light-updates: false clear-tick-list: true #停用隨機刻的方塊 ID disable-block-ticking: #- 2 # grass chunk-generation: #等待生成的區塊隊列的最大長度 queue-size: 2 #等待填充的區塊隊列的最大長度 population-queue-size: 2 ticks-per: animal-spawns: 400 monster-spawns: 1 autosave: 6000 cache-cleanup: 900 spawn-limits: monsters: 70 animals: 15 water-animals: 5 ambient: 15 auto-report: #發送錯誤報告 enabled: false send-code: true send-settings: true send-phpinfo: false host: crash.pocketmine.net anonymous-statistics: #發送用於數據收集和插件使用情況統計的匿名數據 enabled: true host: stats.pocketmine.net commands: #您可以在此處自訂伺服器的指令 #在此處指定指令的名稱,以覆蓋預設值。 #如果沒有為某個指令設定自訂值,將會為該指令應用預設值。 #註意:部分指令無法在此被停用,例如 /stop、/reload 等重要指令。 default: true #在此處停用指令 #例如,刪除下面註釋前的井號將會停用 /version 和 /plugins 指令 #version: false #plugins: false timings: #選擇查看您的計時數據的主機。 host: timings.pmmp.io aliases: #範例: #showtheversion: version #savestop: [save-all, stop] worlds: #這些設定將會覆蓋 server.properties 中的生成器設定,並允許讀取多個世界。 #範例: #world: # seed: 404 # generator: FLAT:2;7,59x1,3x3,2;1;decoration(treecount=80 grasscount=45) [ { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 270, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "03a2ec83-9100-3990-8c3c-ea27b9f23e57" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 269, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "01c36612-a971-348e-8ac7-37858e3342a5" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 271, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2d81ddd7-f88f-3b37-8972-b55b1aa2cd72" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 290, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "37c2d1d6-9ed6-366c-8628-abc741206353" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 274, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "0b3b84e7-6313-3b25-81fc-e493c7436fc1" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 273, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "7610780d-98b8-3cac-8dab-5dea37b11d5f" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 275, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "0fcaf19f-29cd-309e-867d-4735a48390f2" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 291, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "5975e130-082a-34e9-8264-31cfa52bb7be" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 257, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9b5c79ae-e7c6-3209-8018-68983175bc8e" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 256, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "8155e710-39ff-3a2f-834e-027d9bcff380" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 258, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2b89db67-fb1e-303a-8666-13e9fdb4cdfd" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 292, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9162773c-ddc6-3408-8e88-2b6f72375f75" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 278, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "6a9c2678-4876-33c1-825a-878cc60ebe88" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 277, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "18b70e06-b950-3e75-8792-02de950840b3" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 279, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e28fb66f-d92c-3ff0-8ab5-aee5e3812f40" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 293, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "72ed2ed7-7011-32c1-8c32-d6a49810690b" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 285, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "5395e2ae-c767-3f38-8cb0-23d106a516c2" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 284, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "5b4962ce-f3b8-3c46-81aa-cde7d29e752a" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 286, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "193e8cc8-200e-3ded-8e56-2e89038a8c3f" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 294, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "7a911276-2e46-31a7-8abf-28e8aa915153" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 359, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e4cfc5d9-92a6-3358-8dc4-773e6b6c76dd" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 268, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "02d88794-e123-3ccb-86a5-ad6d3fcfac72" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 272, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "3b43b7fb-fce9-3bb7-8023-51d61a09cc50" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 267, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "619da88f-cfaa-3aaf-8b32-29d21114b8e8" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 276, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "046b19e4-0056-3bb2-84e9-cb181c8766e7" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 283, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "3fd41795-5773-39d0-8579-8832e95a526a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 261, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "64b8bdf6-6876-3ecc-8dd6-84815b873afe" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 318, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 288, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "dd18c991-c60e-39d6-8c8d-bcce01d93f95" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 5, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 6, "count": 8, "nbt": "" } ], "uuid": "7f8b1b52-7e39-3eef-861e-b85dc1e65aa4" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 6, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 7, "count": 8, "nbt": "" } ], "uuid": "0b9a0289-c850-33e9-818b-494e075caf5d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 7, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 8, "count": 8, "nbt": "" } ], "uuid": "bed42f19-e8d0-3f7e-897e-31de084ffb8f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 8, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 9, "count": 8, "nbt": "" } ], "uuid": "10a29425-fac9-3872-8c9e-bc10544a3233" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 9, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 10, "count": 8, "nbt": "" } ], "uuid": "f18e28e0-beb8-3711-899b-e8dee4d4c26d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 10, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 11, "count": 8, "nbt": "" } ], "uuid": "2ec729ac-1b3f-376e-8df2-584ff08ab2e6" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 11, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 12, "count": 8, "nbt": "" } ], "uuid": "cb6937fa-c652-3d97-8716-4bccc41ccfe5" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 12, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 13, "count": 8, "nbt": "" } ], "uuid": "a7749606-3efb-300a-8342-00552bea665a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 13, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 14, "count": 8, "nbt": "" } ], "uuid": "2a44fad8-b4c9-39cb-8e13-8f64d9e713d4" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 14, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 15, "count": 8, "nbt": "" } ], "uuid": "65a8cfc2-f222-3d9f-81fe-3435dd4865cb" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 15, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 16, "count": 8, "nbt": "" } ], "uuid": "3fbf008a-cc5e-3ac9-881b-cc121a3cc7dc" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 16, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 17, "count": 8, "nbt": "" } ], "uuid": "1df353dc-4b20-39d2-869e-50589d752f78" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 17, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 18, "count": 8, "nbt": "" } ], "uuid": "cefec130-914b-37d1-87ac-26d1fd9a29c9" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 18, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 19, "count": 8, "nbt": "" } ], "uuid": "e8b4ec0d-a930-3882-87c9-295217df35c2" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 19, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 20, "count": 8, "nbt": "" } ], "uuid": "805d879b-4ce0-3b74-8c70-cc2b49413b2a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 20, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 21, "count": 8, "nbt": "" } ], "uuid": "50f54f68-c4a3-3727-8ebe-6ef3bd434752" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 21, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 22, "count": 8, "nbt": "" } ], "uuid": "f73673bd-ed2f-345a-81e3-c465ffaaa392" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 22, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 23, "count": 8, "nbt": "" } ], "uuid": "7703a0c8-780e-3475-82dd-6acb60e558be" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 23, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 24, "count": 8, "nbt": "" } ], "uuid": "d423d46f-f47c-316b-8107-601705153871" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 24, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 25, "count": 8, "nbt": "" } ], "uuid": "e304dbc6-1317-3713-820c-0acee2567559" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 25, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 26, "count": 8, "nbt": "" } ], "uuid": "a0dccf07-5287-3c94-86d3-9aae214c7fd8" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 26, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 27, "count": 8, "nbt": "" } ], "uuid": "1d5a641c-3ca1-346a-8a07-fa5be6c34871" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 27, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 28, "count": 8, "nbt": "" } ], "uuid": "0074b8bb-d797-3d10-8758-7f08e75f007d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 28, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 29, "count": 8, "nbt": "" } ], "uuid": "b80076c3-ac25-36a6-869d-2374760b299d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 29, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 30, "count": 8, "nbt": "" } ], "uuid": "03e811e7-2260-3c33-88ae-0b9b81ef0c60" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 30, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 31, "count": 8, "nbt": "" } ], "uuid": "bac3d539-f912-324c-82f5-590c1b8b8ea1" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 31, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 32, "count": 8, "nbt": "" } ], "uuid": "8405442f-d83b-347c-8382-5e4c6eebd0c1" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 32, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 33, "count": 8, "nbt": "" } ], "uuid": "805787ed-146e-30c3-8fb8-7d2515f08351" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 33, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 34, "count": 8, "nbt": "" } ], "uuid": "279f326a-e684-3eea-8262-fa022dafa401" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 34, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 35, "count": 8, "nbt": "" } ], "uuid": "a776f80b-d74f-3684-8846-fc9df44a8bbc" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 35, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 36, "count": 8, "nbt": "" } ], "uuid": "7a0d4604-3492-3b14-888a-7ae5a4a698bd" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 441, "damage": 36, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" }, { "id": 262, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 262, "damage": 37, "count": 8, "nbt": "" } ], "uuid": "984b2d77-3953-3af4-818f-3d373e0a0f34" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" }, { "id": 266, "damage": 0, "count": 9, "nbt": "" } ], "output": [ { "id": 41, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "1356c429-44aa-33b6-8a88-20ec26dffae7" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 41, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 266, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "bf7c627d-7d59-3c2f-85b8-ca56a1ed4790" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" }, { "id": 265, "damage": 0, "count": 9, "nbt": "" } ], "output": [ { "id": 42, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "a6453be1-3c6a-3f95-8bf2-3bd515129565" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 42, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 265, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "d424b182-9fa4-3ec9-813b-168a24fd800f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" }, { "id": 264, "damage": 0, "count": 9, "nbt": "" } ], "output": [ { "id": 57, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "442c9f20-abb1-3be3-8ed6-655f87e0dbaf" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 57, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 264, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "4078c7fe-7a37-309e-8726-f12886d8d7a5" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" }, { "id": 388, "damage": 0, "count": 9, "nbt": "" } ], "output": [ { "id": 133, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9db87519-563b-3c9d-8d59-b6f7b06ea0ba" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 133, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 388, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "e95518ee-dd32-3ad6-84f4-4615b191caad" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" }, { "id": 331, "damage": 0, "count": 9, "nbt": "" } ], "output": [ { "id": 152, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "aacb0a6f-b322-3d88-88e6-6bcf5bf853c3" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 152, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 331, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "901be0dc-08e2-3b93-8269-e5a2da6f7d6f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" }, { "id": 351, "damage": 4, "count": 9, "nbt": "" } ], "output": [ { "id": 22, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "f3c2b4e3-1b63-3d8d-83b9-d0a8964178d7" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 22, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 4, "count": 9, "nbt": "" } ], "uuid": "9a258f0f-ff92-31a5-8f46-60c137724439" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" }, { "id": 296, "damage": 0, "count": 9, "nbt": "" } ], "output": [ { "id": 170, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "02edac83-861b-3991-8e9c-b0a0d765c3a7" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 170, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 296, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "ea392e0e-0688-3567-8251-8a3b55ac0829" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" }, { "id": 263, "damage": 0, "count": 9, "nbt": "" } ], "output": [ { "id": 173, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "a20df460-d4b3-339c-86e6-bcab7304800a" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 173, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 263, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "0810c2ee-c2b8-3363-8c0d-2c0d1f4651f4" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "6fbf1dae-9aa7-3c5b-8aa3-08748b774605" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 371, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "aa6c76c2-9b7f-3455-8ba3-59c5f5db9f38" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" }, { "id": 452, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "42dec36d-c7f5-38f2-85d1-8f5920badc29" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 452, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "1f3787ea-e195-3d9f-8530-becfe3aba799" }, { "type": 0, "input": [ { "id": 39, "damage": -1, "count": 1, "nbt": "" }, { "id": 40, "damage": -1, "count": 1, "nbt": "" }, { "id": 281, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 282, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "74b6d126-7791-35ac-8e95-b4a1332f7087" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 457, "damage": 0, "count": 1, "nbt": "" }, { "id": 457, "damage": 0, "count": 1, "nbt": "" }, { "id": 457, "damage": 0, "count": 1, "nbt": "" }, { "id": 457, "damage": 0, "count": 1, "nbt": "" }, { "id": 457, "damage": 0, "count": 1, "nbt": "" }, { "id": 457, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 281, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 459, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "22c9ddbd-e855-3a8f-8d54-ef4fb5b2cf99" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 296, "damage": 0, "count": 1, "nbt": "" }, { "id": 351, "damage": 3, "count": 1, "nbt": "" }, { "id": 296, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 357, "damage": 0, "count": 8, "nbt": "" } ], "uuid": "1afc6e18-14c3-3914-898d-7b90a893e578" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 103, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9eb4cf3f-2735-36fb-82b9-3c45830ead87" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 103, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 360, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "734f5b98-7884-3155-8513-1343dcdfb80b" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 360, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 362, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e7d3e730-172e-3841-88f0-17f01873142f" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 86, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 361, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "763a23af-256a-32bd-8b44-a8045ffad43d" }, { "type": 0, "input": [ { "id": 86, "damage": -1, "count": 1, "nbt": "" }, { "id": 353, "damage": 0, "count": 1, "nbt": "" }, { "id": 344, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 400, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9f68e787-88e6-3610-8c52-e340d2e3efdf" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 375, "damage": 0, "count": 1, "nbt": "" }, { "id": 353, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 39, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 376, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "442bf7cf-f6a6-3ed0-842d-b8ce0a8d0297" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 54, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "72a62107-c6ce-3737-869d-d93784df70e2" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 131, "damage": -1, "count": 1, "nbt": "" }, { "id": 54, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 146, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "b7360d3c-7f23-3058-8df3-f29392cdc251" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 61, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "ff205f2e-b109-3950-8dd7-62bb6c3d0896" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 58, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "bd882bfe-73b4-3b1e-851e-b5e4a55e568d" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 12, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 24, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "d3e58a07-86b7-3279-89dd-88b598bdf1dc" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 12, "damage": 1, "count": 1, "nbt": "" }, { "id": 12, "damage": 1, "count": 1, "nbt": "" }, { "id": 12, "damage": 1, "count": 1, "nbt": "" }, { "id": 12, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 179, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "92f87d28-ff66-3b24-8782-1fd4b064e1ae" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 24, "damage": 2, "count": 4, "nbt": "" } ], "uuid": "75333d2f-bd68-3c25-8e77-d387d7ebe524" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 179, "damage": 2, "count": 4, "nbt": "" } ], "uuid": "9696e9b4-9364-3e84-830f-8d61250ea737" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 44, "damage": 1, "count": 1, "nbt": "" }, { "id": 44, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 24, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "6c299bbc-b63b-3bc3-8e9a-bc9c2c9bf478" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 182, "damage": 0, "count": 1, "nbt": "" }, { "id": 182, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 179, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "d41b919b-0978-30b3-8005-229dc557462b" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 1, "damage": 0, "count": 1, "nbt": "" }, { "id": 1, "damage": 0, "count": 1, "nbt": "" }, { "id": 1, "damage": 0, "count": 1, "nbt": "" }, { "id": 1, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 98, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "b87de2c9-874e-39c7-8f0b-465290487184" }, { "type": 0, "input": [ { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 106, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 98, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "2e8977f7-55aa-38e7-85e3-8c7f9f7163b0" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 44, "damage": 5, "count": 1, "nbt": "" }, { "id": 44, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 98, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "ace0bfb6-8096-3827-883e-92ef0bcc7ccf" }, { "type": 0, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 106, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 48, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "63a7aebf-8fa7-3af1-835c-e1e42c126c7b" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 101, "damage": 0, "count": 16, "nbt": "" } ], "uuid": "e1758921-41fc-3789-8da4-05a710e51682" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 102, "damage": 0, "count": 16, "nbt": "" } ], "uuid": "3f9c1113-e734-35ce-8175-e57eaa68e8b6" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 405, "damage": 0, "count": 1, "nbt": "" }, { "id": 405, "damage": 0, "count": 1, "nbt": "" }, { "id": 405, "damage": 0, "count": 1, "nbt": "" }, { "id": 405, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 112, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "378aca88-7b4c-3281-82fa-214ff3801826" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 155, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2e3d50f3-57fc-32bc-852a-09a0907af4f4" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 44, "damage": 6, "count": 1, "nbt": "" }, { "id": 44, "damage": 6, "count": 1, "nbt": "" } ], "output": [ { "id": 155, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "c4014884-56c0-39f7-8768-1247eedb9d6d" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 155, "damage": 0, "count": 1, "nbt": "" }, { "id": 155, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 155, "damage": 2, "count": 2, "nbt": "" } ], "uuid": "30a91aa0-7ef1-358c-8c7e-d4ea4531680e" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 182, "damage": 1, "count": 1, "nbt": "" }, { "id": 182, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 201, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "c726f36c-737a-3101-8860-6adfe70e07ac" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 1, "damage": 3, "count": 2, "nbt": "" } ], "uuid": "8f6353eb-9aa3-36a7-8c56-fb08ba6d970a" }, { "type": 0, "input": [ { "id": 1, "damage": 3, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 1, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "771afe58-6f20-38ac-84f6-dd62d7ac22fd" }, { "type": 0, "input": [ { "id": 1, "damage": 3, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 1, "damage": 5, "count": 2, "nbt": "" } ], "uuid": "097c03f1-8edc-3342-8d12-bf065bf98116" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 1, "damage": 3, "count": 1, "nbt": "" }, { "id": 1, "damage": 3, "count": 1, "nbt": "" }, { "id": 1, "damage": 3, "count": 1, "nbt": "" }, { "id": 1, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 1, "damage": 4, "count": 4, "nbt": "" } ], "uuid": "d0b3861b-ab0c-3151-8746-e824917c342c" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 1, "damage": 1, "count": 1, "nbt": "" }, { "id": 1, "damage": 1, "count": 1, "nbt": "" }, { "id": 1, "damage": 1, "count": 1, "nbt": "" }, { "id": 1, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 1, "damage": 2, "count": 4, "nbt": "" } ], "uuid": "957d79e4-c302-321f-8ea4-f51d20324386" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 1, "damage": 5, "count": 1, "nbt": "" }, { "id": 1, "damage": 5, "count": 1, "nbt": "" }, { "id": 1, "damage": 5, "count": 1, "nbt": "" }, { "id": 1, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 1, "damage": 6, "count": 4, "nbt": "" } ], "uuid": "480332c0-8540-3ae2-8a94-2261e925bd1f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 399, "damage": 0, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 138, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "86baeddc-3bd9-3d36-8e69-2779b7586bb3" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 381, "damage": 0, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 130, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "d360cc20-1d94-34b1-879a-d9ad6fb8dedf" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 445, "damage": 0, "count": 1, "nbt": "" }, { "id": 54, "damage": -1, "count": 1, "nbt": "" }, { "id": 445, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 10, "count": 1, "nbt": "" } ], "uuid": "b1ae74f1-efd5-38d6-8a32-86787ec44d1b" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 15, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 14, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 13, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 12, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 11, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 10, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 6, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 9, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 7, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 8, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 8, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 7, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 9, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 6, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 10, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 5, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 11, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 4, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 12, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 13, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 14, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 5, "input": [ { "id": 218, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 218, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "b3840292-a2a7-4762-8814-32e3e7ee7dc0" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 0, "count": 8, "nbt": "" } ], "uuid": "e91bef0b-9a5f-36ae-8a28-dce774b3334f" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 14, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 1, "count": 8, "nbt": "" } ], "uuid": "bb6d078c-8b90-35ac-86f2-8944ce81ffcd" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 13, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 2, "count": 8, "nbt": "" } ], "uuid": "1aa350ea-9b0f-34b0-837c-48ac644e60ef" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 12, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 3, "count": 8, "nbt": "" } ], "uuid": "0a792d2a-562e-3d3b-8410-1bfb0c6f1c66" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 11, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 4, "count": 8, "nbt": "" } ], "uuid": "0da1379d-0221-39b7-84e8-b2ae9c19c871" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 10, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 5, "count": 8, "nbt": "" } ], "uuid": "d3cdb63c-6e7d-366d-8749-cb4a5682ef3b" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 9, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 6, "count": 8, "nbt": "" } ], "uuid": "22c8f7f7-752e-3095-821e-2d0380cb2de7" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 8, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 7, "count": 8, "nbt": "" } ], "uuid": "1128466f-4c63-3675-8a2f-db17a357a168" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 7, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 8, "count": 8, "nbt": "" } ], "uuid": "b9a457ba-a22d-3fcb-85b7-5effeeaf7099" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 6, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 9, "count": 8, "nbt": "" } ], "uuid": "6a409514-e8f6-33ee-893d-feaa86b50025" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 5, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 10, "count": 8, "nbt": "" } ], "uuid": "87861385-8e5a-3263-8a8e-d6d3afeab124" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 4, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 11, "count": 8, "nbt": "" } ], "uuid": "5c63d7b6-0b3a-31da-83e0-6afe28b79ba0" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 3, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 12, "count": 8, "nbt": "" } ], "uuid": "d90d8992-d6ab-38e0-8c23-784883443638" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 2, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 13, "count": 8, "nbt": "" } ], "uuid": "feca41ae-196e-3bdc-85a6-3a318bee5073" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 14, "count": 8, "nbt": "" } ], "uuid": "412d5351-99be-3455-8099-816478fb8ccd" }, { "type": 0, "input": [ { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 0, "count": 0, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 13, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 237, "damage": 15, "count": 8, "nbt": "" } ], "uuid": "39494eef-a542-312c-8d11-56433d390569" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 298, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "6107bf2b-34d0-3a65-839d-8f73488eed91" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 299, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "b65af40a-231c-3548-8051-d8bcaff57a6d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 300, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e10380e9-3aa2-34b5-8a6e-527669e5f38d" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 301, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "0732ca55-da1c-337c-8090-0623009b70d9" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 306, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2285ecc1-a66d-3169-824a-e8f43291d8f2" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 307, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "40c3ea66-391e-3922-8883-a383a3221a06" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 308, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "c6dd77a6-5e6f-328e-8750-0fd04a295487" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 309, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e5f898f3-2c4e-3cc9-8033-7dcdf25fa9af" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 310, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "6d2a6e50-5173-36c5-8bb9-e15e93053c5a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 311, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "c9270466-caa7-3a5f-85fb-24bc9e20e584" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 312, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "67f3cf7b-8743-320e-8017-e597bacc0961" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 313, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "c98b2ecc-7a82-3e2a-80aa-cc7aecd9136b" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 314, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "6af4e757-4aa0-3110-83fa-a3f48c15a951" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 315, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "df2e3237-722a-31f9-8723-420a70a3ab09" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 316, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "959742e7-311b-38e6-8769-1d74dbc3b5c1" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 317, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "8f9ed679-e5fc-309e-8ed6-c063c03423d3" }, { "type": 0, "input": [ { "id": 351, "damage": 0, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 15, "count": 1, "nbt": "" } ], "uuid": "4f9126d2-62c8-38ed-8de1-8d70f7a68132" }, { "type": 0, "input": [ { "id": 351, "damage": 1, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 14, "count": 1, "nbt": "" } ], "uuid": "99180265-f015-340d-8dab-30eefadc16eb" }, { "type": 0, "input": [ { "id": 351, "damage": 2, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 13, "count": 1, "nbt": "" } ], "uuid": "dd5acc53-72f5-3e79-8dc5-2450976af0dc" }, { "type": 0, "input": [ { "id": 351, "damage": 3, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 12, "count": 1, "nbt": "" } ], "uuid": "427118ad-4bab-3401-8f88-4eba3e93bb21" }, { "type": 0, "input": [ { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 11, "count": 1, "nbt": "" } ], "uuid": "d200a0ec-e2f2-3b50-829d-04e20a4f0091" }, { "type": 0, "input": [ { "id": 351, "damage": 5, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 10, "count": 1, "nbt": "" } ], "uuid": "e6f5cd28-c745-3d99-88b3-6d739d53e4cf" }, { "type": 0, "input": [ { "id": 351, "damage": 6, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 9, "count": 1, "nbt": "" } ], "uuid": "915407da-0932-398b-898f-394f49827641" }, { "type": 0, "input": [ { "id": 351, "damage": 7, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 8, "count": 1, "nbt": "" } ], "uuid": "6d322192-0a90-3d5d-8ef0-4554bb197702" }, { "type": 0, "input": [ { "id": 351, "damage": 8, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 7, "count": 1, "nbt": "" } ], "uuid": "1f7d7c63-b0f2-359d-8ccf-012fd70b0f66" }, { "type": 0, "input": [ { "id": 351, "damage": 9, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 6, "count": 1, "nbt": "" } ], "uuid": "7839d797-8325-39c2-8b7c-93f75dacb40a" }, { "type": 0, "input": [ { "id": 351, "damage": 10, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 5, "count": 1, "nbt": "" } ], "uuid": "6f3ad575-beec-3305-8f71-86b6867c87da" }, { "type": 0, "input": [ { "id": 351, "damage": 11, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 4, "count": 1, "nbt": "" } ], "uuid": "7f573801-1a96-3c0b-8e23-192c0c9c901b" }, { "type": 0, "input": [ { "id": 351, "damage": 12, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "d1453300-e0bc-3c35-8588-a3149abaf79f" }, { "type": 0, "input": [ { "id": 351, "damage": 13, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "bb77b7ba-3239-3b46-8976-af769b18782c" }, { "type": 0, "input": [ { "id": 351, "damage": 14, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "2f9bc3a5-0ed4-3916-8a3a-d888b5d1ba9e" }, { "type": 0, "input": [ { "id": 351, "damage": 15, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "aea7b9d9-9994-3eca-8bf6-c92c2c7078c5" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 37, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 11, "count": 1, "nbt": "" } ], "uuid": "0531f5f8-4df3-3479-8767-9226627c7183" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 244, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 1, "count": 2, "nbt": "" } ], "uuid": "728ffba3-b82e-34cf-887e-eb134b8113ae" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "415a597e-4c5c-389c-83cd-91128fae791a" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 352, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 15, "count": 3, "nbt": "" } ], "uuid": "b01e02bb-b020-39d7-8065-18e9a6eae776" }, { "type": 0, "input": [ { "id": 351, "damage": 1, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 9, "count": 2, "nbt": "" } ], "uuid": "744c3a79-8856-3e34-81b8-012f40623603" }, { "type": 0, "input": [ { "id": 351, "damage": 1, "count": 1, "nbt": "" }, { "id": 351, "damage": 11, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 14, "count": 2, "nbt": "" } ], "uuid": "57b9293d-bb15-3aa1-87a5-04897a138ed3" }, { "type": 0, "input": [ { "id": 351, "damage": 2, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 10, "count": 2, "nbt": "" } ], "uuid": "528b4d59-cf0b-31eb-8d03-28320f6c2a9f" }, { "type": 0, "input": [ { "id": 351, "damage": 0, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 8, "count": 2, "nbt": "" } ], "uuid": "77965137-1d37-3f1d-88ee-96b0004828c4" }, { "type": 0, "input": [ { "id": 351, "damage": 8, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 7, "count": 2, "nbt": "" } ], "uuid": "c49520f2-792c-3aac-880a-a2f9d02cea23" }, { "type": 0, "input": [ { "id": 351, "damage": 0, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 7, "count": 3, "nbt": "" } ], "uuid": "62edba64-56d1-343d-8361-25ed83ab1740" }, { "type": 0, "input": [ { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 12, "count": 2, "nbt": "" } ], "uuid": "fe565389-3918-393c-8590-bfa4759424ae" }, { "type": 0, "input": [ { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 351, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 6, "count": 2, "nbt": "" } ], "uuid": "7aa6cc6c-308f-34cb-8d77-fd3ee7b41353" }, { "type": 0, "input": [ { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 5, "count": 2, "nbt": "" } ], "uuid": "61dbe715-effe-3447-8802-8a76be7cb540" }, { "type": 0, "input": [ { "id": 351, "damage": 5, "count": 1, "nbt": "" }, { "id": 351, "damage": 9, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 13, "count": 2, "nbt": "" } ], "uuid": "2a88118c-6618-352a-8ad8-cc71296dab2d" }, { "type": 0, "input": [ { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" }, { "id": 351, "damage": 9, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 13, "count": 3, "nbt": "" } ], "uuid": "0616f1a6-4d7d-308c-84d0-0382d1ea1f8d" }, { "type": 0, "input": [ { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 13, "count": 4, "nbt": "" } ], "uuid": "a98d14a1-6f00-3f5a-8d9a-8ecd597d5a5a" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 12, "count": 1, "nbt": "" } ], "uuid": "644851ca-2b54-313c-82a2-488cf0b46f65" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 13, "count": 1, "nbt": "" } ], "uuid": "bf84955d-98e7-3f5a-8f8b-c733dc298f69" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 7, "count": 1, "nbt": "" } ], "uuid": "d4e241f3-4b54-3aa8-8a2c-3ffc2d3c022e" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "cbd1d619-46fc-3e97-8143-377b5cf8a0e7" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 14, "count": 1, "nbt": "" } ], "uuid": "a42f0701-d9d8-3ca7-8304-a03748594380" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 6, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 7, "count": 1, "nbt": "" } ], "uuid": "604463cb-8824-37ff-8a54-9fe889b71202" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 7, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 9, "count": 1, "nbt": "" } ], "uuid": "32790556-d237-381c-879d-8c3ac7dfad99" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 38, "damage": 8, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 7, "count": 1, "nbt": "" } ], "uuid": "45f57146-302a-326e-8bc5-a551d94fe7b7" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 175, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 11, "count": 2, "nbt": "" } ], "uuid": "f1bab0a1-4688-3273-8a83-ade650390793" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 175, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 13, "count": 2, "nbt": "" } ], "uuid": "b8f420f6-856a-37db-8b11-f31ee3cae98c" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 175, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 1, "count": 2, "nbt": "" } ], "uuid": "2bea53df-8652-362a-845a-95d030cf93a4" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 175, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 9, "count": 2, "nbt": "" } ], "uuid": "0722193f-deb0-30f1-86e8-d1121de6d8e2" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 457, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 351, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "e78a7664-29d6-36e2-8940-5e24e1aac9ed" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 395, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "881c7354-4bca-3dd2-88d2-e47aeb611b7b" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 345, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 395, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "0e606443-b1d2-304a-816c-f65d5078818d" }, { "type": 4, "uuid": "85939755-ba10-4d9d-84cc-efb7a8e943c4" }, { "type": 4, "uuid": "d392b075-4ba1-40ae-8789-af868d56f6ce" }, { "type": 4, "uuid": "aecd2294-4b94-434b-8667-4499bb2c9327" }, { "type": 4, "uuid": "00000000-0000-0000-8000-000000000001" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 338, "damage": 0, "count": 1, "nbt": "" }, { "id": 338, "damage": 0, "count": 1, "nbt": "" }, { "id": 338, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 339, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "7564cea0-7f76-3c60-89df-b4945c581b47" }, { "type": 0, "input": [ { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 339, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 340, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "84454588-2c88-3feb-80e9-d32b3266e90d" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 139, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "0e0d9254-e7f2-35a1-83be-5f643e0ae362" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 48, "damage": -1, "count": 1, "nbt": "" }, { "id": 48, "damage": -1, "count": 1, "nbt": "" }, { "id": 48, "damage": -1, "count": 1, "nbt": "" }, { "id": 48, "damage": -1, "count": 1, "nbt": "" }, { "id": 48, "damage": -1, "count": 1, "nbt": "" }, { "id": 48, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 139, "damage": 1, "count": 6, "nbt": "" } ], "uuid": "17d77486-9315-3c77-81b9-859cd465d252" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 113, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "e17746c4-d573-3fde-88ae-67db5b908ab8" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 85, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "3a0776bd-7942-3a41-888c-88ccaf9c4438" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 85, "damage": 1, "count": 3, "nbt": "" } ], "uuid": "0d177968-b2be-3a6f-8ac5-94bb65f9e227" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 85, "damage": 2, "count": 3, "nbt": "" } ], "uuid": "50d667df-8728-3710-85f9-1a86777fa772" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 85, "damage": 3, "count": 3, "nbt": "" } ], "uuid": "ab18785b-de0b-3183-8261-4c24f6e6a4e9" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 85, "damage": 4, "count": 3, "nbt": "" } ], "uuid": "341de627-edcd-3aeb-832d-ab77b40930e1" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 85, "damage": 5, "count": 3, "nbt": "" } ], "uuid": "1c43531e-487b-3d81-8d03-cc0824d66a62" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 107, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2756c732-c49c-3ca3-876a-73b8763e1194" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 183, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "c802ba71-b2e5-39e6-86be-20fc1e517008" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 184, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2c918f77-6559-3292-80e6-5fe457f5ed5f" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 185, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "5f35ca1f-0e5a-30e9-8e62-191733d3d954" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 187, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "b7a95969-e765-37aa-837a-d486e52fa4a0" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 186, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "8e07e0ee-3db5-3303-8db4-1c94bcceb407" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 42, "damage": -1, "count": 1, "nbt": "" }, { "id": 42, "damage": -1, "count": 1, "nbt": "" }, { "id": 42, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 145, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "ad0a35ff-6e0e-36e3-8bdc-fc7fd27c06d4" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 340, "damage": 0, "count": 1, "nbt": "" }, { "id": 340, "damage": 0, "count": 1, "nbt": "" }, { "id": 340, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 47, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "0245e503-eaa7-3e7c-86f5-99529ac02dc4" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 332, "damage": 0, "count": 1, "nbt": "" }, { "id": 332, "damage": 0, "count": 1, "nbt": "" }, { "id": 332, "damage": 0, "count": 1, "nbt": "" }, { "id": 332, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 80, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2fdd3bb4-4840-36e8-822a-495bcfc87358" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 80, "damage": -1, "count": 1, "nbt": "" }, { "id": 80, "damage": -1, "count": 1, "nbt": "" }, { "id": 80, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 78, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "907af0ad-f511-3332-8c30-5dd2b9f6c5f7" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 337, "damage": 0, "count": 1, "nbt": "" }, { "id": 337, "damage": 0, "count": 1, "nbt": "" }, { "id": 337, "damage": 0, "count": 1, "nbt": "" }, { "id": 337, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 82, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "8e464b1c-988c-3536-8ba7-f08935b5b0cf" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 336, "damage": 0, "count": 1, "nbt": "" }, { "id": 336, "damage": 0, "count": 1, "nbt": "" }, { "id": 336, "damage": 0, "count": 1, "nbt": "" }, { "id": 336, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 45, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "79a0a1fa-5b5e-390d-80e5-0cd56175a938" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 348, "damage": 0, "count": 1, "nbt": "" }, { "id": 348, "damage": 0, "count": 1, "nbt": "" }, { "id": 348, "damage": 0, "count": 1, "nbt": "" }, { "id": 348, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 89, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "90204e1b-a23d-3394-8b4d-815abc7a8389" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "8da79cbf-0028-3f91-8f4d-cbadf6a11448" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 289, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 289, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 289, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 289, "damage": 0, "count": 1, "nbt": "" }, { "id": 12, "damage": -1, "count": 1, "nbt": "" }, { "id": 289, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 46, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "4caf548b-31ca-35f1-8cb5-813728f5b89a" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 44, "damage": 3, "count": 6, "nbt": "" } ], "uuid": "1462a6cc-337b-3937-8bd8-afa0fa4073dc" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 1, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 44, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "f42858a1-9e42-35ba-89e4-4bf14c06b5fb" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 44, "damage": 1, "count": 6, "nbt": "" } ], "uuid": "3aef2300-361c-3377-8c7a-44c0d52415bb" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 182, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "f0558f56-c774-3c37-8a91-3b61ab89af91" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 158, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "956e2ce9-94c2-386a-8905-e4a8cc316b39" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 158, "damage": 1, "count": 6, "nbt": "" } ], "uuid": "bbb016eb-f50d-33e1-8b91-6bbd4c443b67" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 158, "damage": 2, "count": 6, "nbt": "" } ], "uuid": "d516ed3d-26ba-39d1-8154-da7305967905" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 158, "damage": 3, "count": 6, "nbt": "" } ], "uuid": "64d86ffe-770c-3ba5-83df-8c3b5952e813" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 158, "damage": 4, "count": 6, "nbt": "" } ], "uuid": "6f910a00-6f00-3b53-893f-e36f2018cd7c" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 158, "damage": 5, "count": 6, "nbt": "" } ], "uuid": "0e951c65-f395-3850-8dd0-5637a3d78aef" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 45, "damage": -1, "count": 1, "nbt": "" }, { "id": 45, "damage": -1, "count": 1, "nbt": "" }, { "id": 45, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 44, "damage": 4, "count": 6, "nbt": "" } ], "uuid": "589dfb01-0997-319a-89ab-4288f1655c19" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 98, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 44, "damage": 5, "count": 6, "nbt": "" } ], "uuid": "8987bdc3-b389-36a0-8098-9db75d2b6a3a" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 44, "damage": 7, "count": 6, "nbt": "" } ], "uuid": "8270b55e-a43e-3855-8632-b5d28fc0184d" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 155, "damage": -1, "count": 1, "nbt": "" }, { "id": 155, "damage": -1, "count": 1, "nbt": "" }, { "id": 155, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 44, "damage": 6, "count": 6, "nbt": "" } ], "uuid": "91cc4e84-3afa-38c3-8fdb-bae954c85ce9" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 201, "damage": -1, "count": 1, "nbt": "" }, { "id": 201, "damage": -1, "count": 1, "nbt": "" }, { "id": 201, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 182, "damage": 1, "count": 6, "nbt": "" } ], "uuid": "fcf94c46-abcc-3e52-8319-70be867bf0a0" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 433, "damage": 0, "count": 1, "nbt": "" }, { "id": 433, "damage": 0, "count": 1, "nbt": "" }, { "id": 433, "damage": 0, "count": 1, "nbt": "" }, { "id": 433, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 201, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "6cbe2bec-12cb-3a58-8491-e3c804a8fcd3" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 369, "damage": 0, "count": 1, "nbt": "" }, { "id": 433, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 208, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "79afe6f2-f26f-39ea-8c35-1e311547e5d9" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 121, "damage": -1, "count": 1, "nbt": "" }, { "id": 121, "damage": -1, "count": 1, "nbt": "" }, { "id": 121, "damage": -1, "count": 1, "nbt": "" }, { "id": 121, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 206, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "b5bff475-2d8e-34f8-846c-317cd34e579d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 65, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "815b9f45-a4ba-37d2-8819-c7904f3f5254" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 324, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "cdeacb3a-2c5c-37e1-8b5c-8c1099e17ba2" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 427, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "fd19ac9e-3a36-3284-8bee-46339638e9de" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 428, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "51d03994-e169-3622-8ff9-16c87f830fb0" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 429, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "de0d0ac1-b4a4-36a1-813b-185201c4a840" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 430, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "d2976f7f-feda-3afd-8efd-cbf0afba05dc" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 431, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "a203b1c1-4eb0-382a-89d6-e14aec8faac9" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 96, "damage": 0, "count": 2, "nbt": "" } ], "uuid": "3009538d-b39c-39ba-8cbe-a77dc78fdfce" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 167, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "085f73b0-9a36-3569-85b3-a459642fd8f6" }, { "type": 1, "width": 1, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 131, "damage": 0, "count": 2, "nbt": "" } ], "uuid": "9a6577ce-09f4-3ff7-8ca4-063c0022b00a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 25, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9f0ec757-c2de-38e9-8552-9de95f62dd1d" }, { "type": 1, "width": 2, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 330, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "47cd8d5f-4c34-3d43-8612-4310aff754ef" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 323, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "258fa5d9-153a-36a4-8af3-20691efac15a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 325, "damage": 1, "count": 1, "nbt": "" }, { "id": 325, "damage": 1, "count": 1, "nbt": "" }, { "id": 325, "damage": 1, "count": 1, "nbt": "" }, { "id": 353, "damage": 0, "count": 1, "nbt": "" }, { "id": 344, "damage": 0, "count": 1, "nbt": "" }, { "id": 353, "damage": 0, "count": 1, "nbt": "" }, { "id": 296, "damage": 0, "count": 1, "nbt": "" }, { "id": 296, "damage": 0, "count": 1, "nbt": "" }, { "id": 296, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 354, "damage": 0, "count": 1, "nbt": "" }, { "id": 325, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "abc50fc4-08f5-3acd-8fd2-3f5c6268496e" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 338, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 353, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "19efcdbe-ffe2-3176-8bc0-63bb72212d1c" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 391, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 396, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e6220897-85dd-368d-8f49-401e94d293ef" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 360, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" }, { "id": 371, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 382, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "ae0e8212-fce2-3c90-8c11-4dc82d952f40" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 369, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 377, "damage": 0, "count": 2, "nbt": "" } ], "uuid": "513e182c-ffd0-3190-85cb-a841074dc83f" }, { "type": 0, "input": [ { "id": 377, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 378, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "95a74a64-8d63-3cd6-8386-b16b18b35a30" }, { "type": 0, "input": [ { "id": 39, "damage": -1, "count": 1, "nbt": "" }, { "id": 353, "damage": 0, "count": 1, "nbt": "" }, { "id": 375, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 376, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "4dc97c7e-f680-3b0d-862b-55f03542f4a2" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 17, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 5, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "80bea43e-4b5b-3a73-8742-d3d7ba5a79f3" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 17, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 5, "damage": 1, "count": 4, "nbt": "" } ], "uuid": "8263c131-78a2-3351-83e2-1a917e3b98cf" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 17, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 5, "damage": 2, "count": 4, "nbt": "" } ], "uuid": "fb3c6f37-ebe7-3cee-8530-f27a5595a491" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 17, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 5, "damage": 3, "count": 4, "nbt": "" } ], "uuid": "f665ebc3-2a58-3a96-8ff3-2e58e1c07cbc" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 162, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 5, "damage": 4, "count": 4, "nbt": "" } ], "uuid": "2110b7c1-1e2f-3829-8253-a285631de550" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 162, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 5, "damage": 5, "count": 4, "nbt": "" } ], "uuid": "d42c621c-c107-363c-882f-b7dc043231a1" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 280, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "63181647-6d4b-33c3-8984-994c0a553b9e" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 263, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 50, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "acbe306b-bd58-3c21-8f66-68d12747c459" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 263, "damage": 1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 50, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "ce611f89-166c-3435-8a33-65bceb9ab5ee" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 281, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "25f9ccf3-54a6-386f-8c71-4a45dee64683" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 412, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 391, "damage": 0, "count": 1, "nbt": "" }, { "id": 393, "damage": 0, "count": 1, "nbt": "" }, { "id": 39, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 281, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 413, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "f07d4e08-7151-339c-8ec5-18f6cbd51ab8" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 412, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 391, "damage": 0, "count": 1, "nbt": "" }, { "id": 393, "damage": 0, "count": 1, "nbt": "" }, { "id": 40, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 281, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 413, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9bb2add9-41dd-3fc5-8087-addb8794066c" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 374, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "950c1520-1309-38a0-8587-275ec56bd2db" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 336, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 336, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 336, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 390, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "4d5d2e87-6478-3482-8642-7817240ec23f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 66, "damage": 0, "count": 16, "nbt": "" } ], "uuid": "24204c9f-860c-3f45-83a3-2c96df7772d8" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 27, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "184abe3b-1999-3b56-8483-5d211f42950f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 70, "damage": -1, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 28, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "503f2317-2b35-3007-8b8b-52bbff1dad60" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 76, "damage": -1, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 126, "damage": 0, "count": 6, "nbt": "" } ], "uuid": "fb5e6f29-7de6-31e7-885d-9e78375e524b" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 328, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "0985d889-64f8-356b-8999-190368b9b0fd" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 54, "damage": -1, "count": 1, "nbt": "" }, { "id": 328, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 342, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "c75ee4c5-a9a7-3bf1-8589-fc95b1368b19" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 410, "damage": 0, "count": 1, "nbt": "" }, { "id": 328, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 408, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "20f1934d-13b7-374d-8add-fdad0e6c154d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 380, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "59c04eca-a117-3214-877d-54f2eb055886" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 369, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 379, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "16ca18c9-04b3-3388-8cc2-2d0d66f0f011" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 86, "damage": -1, "count": 1, "nbt": "" }, { "id": 50, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 91, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "c51d8eac-c531-3f1d-8c6b-3cc3ed14ec2c" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 269, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 333, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "3038c893-45c3-3796-8caa-cb4f229c0ad8" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 269, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 333, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "836fec7d-fc3b-3117-8eb1-6f3fca4a7f4d" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 269, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 333, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "fdef2459-cb66-38b2-876a-700f0fe3da86" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 269, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 333, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "14560e13-b9de-3052-800d-3e1eb8b2d210" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 269, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 333, "damage": 4, "count": 1, "nbt": "" } ], "uuid": "f159fca3-e550-3ea0-8edb-5e96a5034988" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 269, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 333, "damage": 5, "count": 1, "nbt": "" } ], "uuid": "ab550ba4-bb4c-3a07-8054-4ec054aa8cfc" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 325, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "6d5b7914-1b88-355c-82c7-9b749e20fe46" }, { "type": 0, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 318, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 259, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "be281be6-2c3f-3345-8a8a-edc468b7a468" }, { "type": 1, "width": 3, "height": 1, "input": [ { "id": 296, "damage": 0, "count": 1, "nbt": "" }, { "id": 296, "damage": 0, "count": 1, "nbt": "" }, { "id": 296, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 297, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "8458aa2a-aec1-3d69-88c3-e33a668d22a0" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 53, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "bee29bd1-5348-3b91-8504-ab0ba09702fe" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 134, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "87049e68-1483-3905-87e2-6ffd107cb9ce" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 135, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "0daca645-d201-327b-8516-304a699cda94" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 136, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "b6707fa4-2903-3122-8223-1896207db337" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 163, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "d3177aa1-5db8-3f29-8ede-b7ee7df1184e" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 164, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "a5dba333-b38f-3fc4-8f2c-c93e60be4e30" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 67, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "3abde30e-67c2-3c49-8e49-f168c6f8effd" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 45, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 45, "damage": -1, "count": 1, "nbt": "" }, { "id": 45, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 45, "damage": -1, "count": 1, "nbt": "" }, { "id": 45, "damage": -1, "count": 1, "nbt": "" }, { "id": 45, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 108, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "aaa53bab-8fb1-3d8e-898d-0ed32a61bf48" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" }, { "id": 24, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 128, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "70abe846-d105-34fa-8196-411619e9dede" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" }, { "id": 179, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 180, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "e8526eaa-44f7-3796-81eb-c7dc85f9986f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 98, "damage": -1, "count": 1, "nbt": "" }, { "id": 98, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 109, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "0386a6fe-4680-338e-8459-f59b0f5f06de" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 155, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 155, "damage": -1, "count": 1, "nbt": "" }, { "id": 155, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 155, "damage": -1, "count": 1, "nbt": "" }, { "id": 155, "damage": -1, "count": 1, "nbt": "" }, { "id": 155, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 156, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "ac140bc1-226e-3a91-856a-1f571d6c5381" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" }, { "id": 112, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 114, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "91b8d5fe-268d-3e73-8c45-0707002f8f6e" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 201, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 201, "damage": -1, "count": 1, "nbt": "" }, { "id": 201, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 201, "damage": -1, "count": 1, "nbt": "" }, { "id": 201, "damage": -1, "count": 1, "nbt": "" }, { "id": 201, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 203, "damage": 0, "count": 4, "nbt": "" } ], "uuid": "40f7326e-d6f2-3c1e-8828-8da2a4c727aa" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 346, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "7a99cc40-90e3-3ffe-8a20-2f4be57acd72" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 346, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 391, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 398, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "c2512a2a-c410-3eb3-8e56-ebf748bf415e" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 35, "damage": -1, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 321, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "2665a0bc-21d0-3105-826e-99bc02682bf3" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 260, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 322, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e480b965-d563-31fa-87d1-3d3d5aa09518" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 41, "damage": -1, "count": 1, "nbt": "" }, { "id": 41, "damage": -1, "count": 1, "nbt": "" }, { "id": 41, "damage": -1, "count": 1, "nbt": "" }, { "id": 41, "damage": -1, "count": 1, "nbt": "" }, { "id": 260, "damage": 0, "count": 1, "nbt": "" }, { "id": 41, "damage": -1, "count": 1, "nbt": "" }, { "id": 41, "damage": -1, "count": 1, "nbt": "" }, { "id": 41, "damage": -1, "count": 1, "nbt": "" }, { "id": 41, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 466, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "8b60334f-cbe6-391b-87cb-decbae6ec83a" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 69, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "ab83220b-e2b9-33c2-8f2f-bf829b880950" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 76, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e7d7ace7-bb40-334b-80ba-e0c073d82795" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 158, "damage": -1, "count": 1, "nbt": "" }, { "id": 158, "damage": -1, "count": 1, "nbt": "" }, { "id": 158, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 151, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e47cf7b7-f0e5-3b47-88b9-0500357ac7df" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 76, "damage": -1, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 76, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 356, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "4c49b373-ed26-30a3-8c0b-6d9dd88dbf6b" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 89, "damage": -1, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 123, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "cd45c297-1023-3fea-8192-b930ab8f8a96" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 347, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "96be7703-e61e-368b-8488-6290fb6191e6" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 345, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "d28fee87-b008-3786-80f5-574edcd1542c" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 1, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 77, "damage": 5, "count": 1, "nbt": "" } ], "uuid": "1a1a9c55-e778-36e1-838b-58b444e2890a" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 143, "damage": 5, "count": 1, "nbt": "" } ], "uuid": "812a56ed-c47a-382b-8421-7eacaf5b5d80" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 1, "damage": 0, "count": 1, "nbt": "" }, { "id": 1, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 70, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9fa7c969-51a2-3804-8239-ba190a2db83e" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 72, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "27e6778f-432c-3a15-8007-9352c195b8d3" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 266, "damage": 0, "count": 1, "nbt": "" }, { "id": 266, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 147, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "4fc2b8ac-549e-3af5-82b7-5d8e2786cc33" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 148, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "92f0db9e-89f7-3cac-824f-becdc77776c6" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 261, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 23, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "29cb9de2-abc0-3357-8c96-c15e8a9e4349" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 125, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "5018b165-43a1-3332-8932-74ac7ad9b894" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 54, "damage": -1, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" } ], "output": [ { "id": 410, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "98330357-d76b-3cf4-8042-3404e65dbc87" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 251, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "67a7fec4-6809-367f-8d78-8a1b8e7a6674" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 265, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" }, { "id": 331, "damage": 0, "count": 1, "nbt": "" }, { "id": 4, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 33, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "05870c0f-49d2-3337-8c55-758184886569" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 33, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 29, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "b655c455-55f5-3a13-81de-c0cb016b7796" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 0, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "05f0feae-2e33-3ade-81b9-d70e489b6edc" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "e7aab9b5-e8f5-3b50-81df-2e04e120fe14" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 1, "count": 1, "nbt": "" }, { "id": 35, "damage": 1, "count": 1, "nbt": "" }, { "id": 35, "damage": 1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "aa061865-8b14-35ed-8ce2-6895bca64089" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 14, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "4d072388-0125-336c-8e1a-6d276e763b4d" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 2, "count": 1, "nbt": "" }, { "id": 35, "damage": 2, "count": 1, "nbt": "" }, { "id": 35, "damage": 2, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "eff2e5d6-75f8-30dd-8d25-99af85474815" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 13, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "fc6962aa-8db2-32a3-82a4-8d4b6515fbdd" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 3, "count": 1, "nbt": "" }, { "id": 35, "damage": 3, "count": 1, "nbt": "" }, { "id": 35, "damage": 3, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "caf6a5af-3b00-361b-8ca5-22f3149779f4" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 12, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 3, "count": 1, "nbt": "" } ], "uuid": "39b1009f-e50e-3e9d-81ed-1a46a45d44b0" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 4, "count": 1, "nbt": "" }, { "id": 35, "damage": 4, "count": 1, "nbt": "" }, { "id": 35, "damage": 4, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 4, "count": 1, "nbt": "" } ], "uuid": "ccd3d70e-b343-35c9-8e7d-13209b5201f4" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 11, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 4, "count": 1, "nbt": "" } ], "uuid": "1cbe6b77-4b5c-3092-8a0d-bd9d064b8af1" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 5, "count": 1, "nbt": "" }, { "id": 35, "damage": 5, "count": 1, "nbt": "" }, { "id": 35, "damage": 5, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 5, "count": 1, "nbt": "" } ], "uuid": "26d679e0-09b2-34bb-86d4-440854967ac4" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 10, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 5, "count": 1, "nbt": "" } ], "uuid": "827b6179-a826-335d-8ff5-cd58f825167b" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 6, "count": 1, "nbt": "" }, { "id": 35, "damage": 6, "count": 1, "nbt": "" }, { "id": 35, "damage": 6, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 6, "count": 1, "nbt": "" } ], "uuid": "1cb9369d-80ef-3367-81d3-f328d02c3aa5" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 9, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 6, "count": 1, "nbt": "" } ], "uuid": "3f4466e2-0470-3bc3-8cf6-d694daedb566" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 7, "count": 1, "nbt": "" }, { "id": 35, "damage": 7, "count": 1, "nbt": "" }, { "id": 35, "damage": 7, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 7, "count": 1, "nbt": "" } ], "uuid": "71bf4ad9-3f9e-322f-87e0-f3f93fd0e557" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 8, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 7, "count": 1, "nbt": "" } ], "uuid": "b1f8f920-98f4-3c83-8eac-2ead09943bf8" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 8, "count": 1, "nbt": "" }, { "id": 35, "damage": 8, "count": 1, "nbt": "" }, { "id": 35, "damage": 8, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 8, "count": 1, "nbt": "" } ], "uuid": "6814f08e-983c-3d3e-8aa2-42a3b26d70b6" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 7, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 8, "count": 1, "nbt": "" } ], "uuid": "74c927a8-7de4-3a56-82b9-1137344c6e6a" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 9, "count": 1, "nbt": "" }, { "id": 35, "damage": 9, "count": 1, "nbt": "" }, { "id": 35, "damage": 9, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 9, "count": 1, "nbt": "" } ], "uuid": "6b109e84-3baa-3072-8f10-a92e83e7e87d" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 6, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 9, "count": 1, "nbt": "" } ], "uuid": "ccac1652-cc2b-3cbf-8f65-d9759723bc00" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 10, "count": 1, "nbt": "" }, { "id": 35, "damage": 10, "count": 1, "nbt": "" }, { "id": 35, "damage": 10, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 10, "count": 1, "nbt": "" } ], "uuid": "bdbcdea8-df68-3c3e-879c-ac982c5b7947" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 10, "count": 1, "nbt": "" } ], "uuid": "485f2041-94eb-3f61-81cd-78ad9aef3f2c" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 11, "count": 1, "nbt": "" }, { "id": 35, "damage": 11, "count": 1, "nbt": "" }, { "id": 35, "damage": 11, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 11, "count": 1, "nbt": "" } ], "uuid": "428db8ba-b8a0-39c8-88a5-edfbf4fef8d0" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 11, "count": 1, "nbt": "" } ], "uuid": "7ad887a9-9bac-303c-82f5-e8831cc54967" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 12, "count": 1, "nbt": "" }, { "id": 35, "damage": 12, "count": 1, "nbt": "" }, { "id": 35, "damage": 12, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 12, "count": 1, "nbt": "" } ], "uuid": "3d34a090-ace0-3c66-846b-76787c649f1d" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 12, "count": 1, "nbt": "" } ], "uuid": "d5ab0045-90fa-3ac3-86b3-adda1f830ad5" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 13, "count": 1, "nbt": "" }, { "id": 35, "damage": 13, "count": 1, "nbt": "" }, { "id": 35, "damage": 13, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 13, "count": 1, "nbt": "" } ], "uuid": "ee7bb225-34ea-31e1-8fdc-7e0d6aeb53e5" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 13, "count": 1, "nbt": "" } ], "uuid": "df5d65b1-2682-3fc6-83b5-f65173cd4839" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 14, "count": 1, "nbt": "" }, { "id": 35, "damage": 14, "count": 1, "nbt": "" }, { "id": 35, "damage": 14, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 14, "count": 1, "nbt": "" } ], "uuid": "8656f9ae-3e65-329f-887f-28c8f3de3712" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 14, "count": 1, "nbt": "" } ], "uuid": "f52191e8-b5a6-3965-8e95-3f2b1a306af2" }, { "type": 1, "width": 3, "height": 2, "input": [ { "id": 35, "damage": 15, "count": 1, "nbt": "" }, { "id": 35, "damage": 15, "count": 1, "nbt": "" }, { "id": 35, "damage": 15, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" }, { "id": 5, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 15, "count": 1, "nbt": "" } ], "uuid": "5a666a63-b1ad-32c2-8ff7-017e53fc3171" }, { "type": 0, "input": [ { "id": 355, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 355, "damage": 15, "count": 1, "nbt": "" } ], "uuid": "62581e62-d8ad-332b-8e62-cdb59ba64538" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 340, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 264, "damage": 0, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" }, { "id": 49, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 116, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "9f1212bc-1224-3ce1-8552-e1f38800df3d" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 415, "damage": 0, "count": 1, "nbt": "" }, { "id": 415, "damage": 0, "count": 1, "nbt": "" }, { "id": 415, "damage": 0, "count": 1, "nbt": "" }, { "id": 415, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 334, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "a1bf8e2c-99da-38de-841a-d66393fe9ea6" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 76, "damage": -1, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 76, "damage": -1, "count": 1, "nbt": "" }, { "id": 406, "damage": 0, "count": 1, "nbt": "" }, { "id": 76, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" }, { "id": 1, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 404, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "bff28523-55b1-3882-823a-f3ed321bd7cc" }, { "type": 1, "width": 1, "height": 2, "input": [ { "id": 46, "damage": -1, "count": 1, "nbt": "" }, { "id": 328, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 407, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "8d50cddd-b1b2-3d1f-8417-03aae236fa5a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" }, { "id": 280, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 389, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "22918042-2ed6-3051-820f-de8e29976ade" }, { "type": 0, "input": [ { "id": 377, "damage": 0, "count": 1, "nbt": "" }, { "id": 368, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 381, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "32c31a3a-e0b7-3aef-83de-59878185b5f0" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 381, "damage": 0, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" }, { "id": 370, "damage": 0, "count": 1, "nbt": "" }, { "id": 20, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 426, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "0264b133-80b8-3a9c-8bf2-351265c97609" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 0, "count": 1, "nbt": "" }, { "id": 35, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "20474707-0d4a-34db-8553-30de76408ab7" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 0, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 15, "count": 8, "nbt": "" } ], "uuid": "8654656c-b6a8-3381-8679-ae129f8c3852" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 1, "count": 1, "nbt": "" }, { "id": 35, "damage": 1, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 1, "count": 3, "nbt": "" } ], "uuid": "e3979433-1c94-33d6-8dd4-9e6786890b54" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 14, "count": 8, "nbt": "" } ], "uuid": "53cde6c7-12b9-31c8-8fe6-a05dc66b4638" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 2, "count": 1, "nbt": "" }, { "id": 35, "damage": 2, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 2, "count": 3, "nbt": "" } ], "uuid": "165638c6-d028-39cf-83af-032c0dab70b8" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 2, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 13, "count": 8, "nbt": "" } ], "uuid": "1e7fb057-3bfc-3e1b-8ae7-27f6d6503394" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 3, "count": 1, "nbt": "" }, { "id": 35, "damage": 3, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 3, "count": 3, "nbt": "" } ], "uuid": "295c2a07-0380-3d60-846b-1990e31965f2" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 3, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 12, "count": 8, "nbt": "" } ], "uuid": "b0bbdbff-f96e-357e-8070-46ad3feb8372" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 4, "count": 1, "nbt": "" }, { "id": 35, "damage": 4, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 4, "count": 3, "nbt": "" } ], "uuid": "3251ad94-d395-3fa3-86a5-3a29f07e5d7e" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 4, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 11, "count": 8, "nbt": "" } ], "uuid": "673d55c4-66a1-3957-84e7-25a863d388d1" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 5, "count": 1, "nbt": "" }, { "id": 35, "damage": 5, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 5, "count": 3, "nbt": "" } ], "uuid": "1d30238f-ee18-322f-832a-192d54abef3e" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 5, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 10, "count": 8, "nbt": "" } ], "uuid": "48eda536-d3de-3db8-82a3-d2d472d26f64" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 6, "count": 1, "nbt": "" }, { "id": 35, "damage": 6, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 6, "count": 3, "nbt": "" } ], "uuid": "c614cd22-8f20-389d-831e-3e6afa587fbc" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 6, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 9, "count": 8, "nbt": "" } ], "uuid": "00664a89-701c-30db-8d47-f209b1ba1617" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 7, "count": 1, "nbt": "" }, { "id": 35, "damage": 7, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 7, "count": 3, "nbt": "" } ], "uuid": "d237713c-dd72-3c6f-8d65-b0587b88649d" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 7, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 8, "count": 8, "nbt": "" } ], "uuid": "e97c1632-0e19-317d-8e8f-c57c8976b45e" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 8, "count": 1, "nbt": "" }, { "id": 35, "damage": 8, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 8, "count": 3, "nbt": "" } ], "uuid": "dae5ee34-021a-36fb-805d-74b5796199a5" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 8, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 7, "count": 8, "nbt": "" } ], "uuid": "7cf7cc36-a15b-330d-864c-fa767b35b13b" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 9, "count": 1, "nbt": "" }, { "id": 35, "damage": 9, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 9, "count": 3, "nbt": "" } ], "uuid": "15fbe750-7a08-3938-8429-1faafc29b83f" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 9, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 6, "count": 8, "nbt": "" } ], "uuid": "231dfd32-c17c-3eed-8602-37cb657dfd47" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 10, "count": 1, "nbt": "" }, { "id": 35, "damage": 10, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 10, "count": 3, "nbt": "" } ], "uuid": "0967d13d-8330-3779-894a-d552ac5bb9a4" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 10, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 5, "count": 8, "nbt": "" } ], "uuid": "4fe37c72-f933-3907-89e7-724672fff0a4" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 11, "count": 1, "nbt": "" }, { "id": 35, "damage": 11, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 11, "count": 3, "nbt": "" } ], "uuid": "2a489cae-80bc-3f41-8d97-bb8021395869" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 11, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 4, "count": 8, "nbt": "" } ], "uuid": "a355200a-06ab-3861-8dde-b72344719f82" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 12, "count": 1, "nbt": "" }, { "id": 35, "damage": 12, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 12, "count": 3, "nbt": "" } ], "uuid": "87756285-64a1-3acd-83f2-37d0396273f3" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 12, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 3, "count": 8, "nbt": "" } ], "uuid": "aa0a3625-2f5b-3930-874b-483ea2d95d9b" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 13, "count": 1, "nbt": "" }, { "id": 35, "damage": 13, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 13, "count": 3, "nbt": "" } ], "uuid": "c6a7fa22-3edb-3737-8d98-12f9c756c794" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 13, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 2, "count": 8, "nbt": "" } ], "uuid": "a0f770a2-3143-3b8c-8962-f8d3196039d1" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 14, "count": 1, "nbt": "" }, { "id": 35, "damage": 14, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 14, "count": 3, "nbt": "" } ], "uuid": "42d3b54b-c04b-355f-8c60-dd28a07c48fd" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 14, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 1, "count": 8, "nbt": "" } ], "uuid": "cf2f821a-71b4-34dd-8d80-2cc1639a5e56" }, { "type": 1, "width": 2, "height": 1, "input": [ { "id": 35, "damage": 15, "count": 1, "nbt": "" }, { "id": 35, "damage": 15, "count": 1, "nbt": "" } ], "output": [ { "id": 171, "damage": 15, "count": 3, "nbt": "" } ], "uuid": "9e15612c-46d9-338b-8009-fa1c6aa03d53" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 351, "damage": 15, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" }, { "id": 172, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 159, "damage": 0, "count": 8, "nbt": "" } ], "uuid": "8ce1418c-8dcb-3915-8ced-9637d364daab" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 165, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "221efd53-51f5-3294-80a1-793cf92ba895" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 165, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 341, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "95124df9-d954-3e96-82cc-cb6225285cd3" }, { "type": 0, "input": [ { "id": 377, "damage": 0, "count": 1, "nbt": "" }, { "id": 263, "damage": 0, "count": 1, "nbt": "" }, { "id": 289, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 385, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "06831c11-296e-30a9-8044-e8706283101e" }, { "type": 0, "input": [ { "id": 377, "damage": 0, "count": 1, "nbt": "" }, { "id": 263, "damage": 1, "count": 1, "nbt": "" }, { "id": 289, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 385, "damage": 0, "count": 3, "nbt": "" } ], "uuid": "1acfc4d3-b95b-3466-83bd-f50c43bb35cf" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" }, { "id": 341, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 287, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 420, "damage": 0, "count": 2, "nbt": "" } ], "uuid": "bd098ac2-705f-302a-8526-b71752bc1ba2" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" }, { "id": 0, "damage": 0, "count": 0, "nbt": "" }, { "id": 334, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 416, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "ac31b835-f39b-3403-8ef8-4bdbe73ccaa6" }, { "type": 1, "width": 2, "height": 2, "input": [ { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 168, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "43930853-2519-3862-88c1-2d110ea7702a" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 168, "damage": 2, "count": 1, "nbt": "" } ], "uuid": "8e9b74f9-3ebc-3030-83af-4e59b12e22ea" }, { "type": 1, "width": 1, "height": 1, "input": [ { "id": 30, "damage": -1, "count": 1, "nbt": "" } ], "output": [ { "id": 287, "damage": 0, "count": 9, "nbt": "" } ], "uuid": "b0a0bb5c-36b9-326d-8ce5-c72bc5394037" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 351, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 168, "damage": 1, "count": 1, "nbt": "" } ], "uuid": "0bceb8ce-b79b-3da5-8b91-93a5fa91db98" }, { "type": 1, "width": 3, "height": 3, "input": [ { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 422, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 422, "damage": 0, "count": 1, "nbt": "" }, { "id": 422, "damage": 0, "count": 1, "nbt": "" }, { "id": 422, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" }, { "id": 422, "damage": 0, "count": 1, "nbt": "" }, { "id": 409, "damage": 0, "count": 1, "nbt": "" } ], "output": [ { "id": 169, "damage": 0, "count": 1, "nbt": "" } ], "uuid": "537886ad-9fff-3f33-878d-0856699a69b0" }, { "type": 2, "inputId": 4, "output": { "id": 1, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 12, "output": { "id": 20, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 14, "output": { "id": 266, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 15, "output": { "id": 265, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 16, "output": { "id": 263, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 17, "output": { "id": 263, "damage": 1, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 21, "output": { "id": 351, "damage": 4, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 56, "output": { "id": 264, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 73, "output": { "id": 331, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 81, "output": { "id": 351, "damage": 2, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 82, "output": { "id": 172, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 87, "output": { "id": 405, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 129, "output": { "id": 388, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 153, "output": { "id": 406, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 162, "output": { "id": 263, "damage": 1, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 256, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 257, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 258, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 267, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 283, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 284, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 285, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 286, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 292, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 294, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 302, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 303, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 304, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 305, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 306, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 307, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 308, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 309, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 314, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 315, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 316, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 317, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 319, "output": { "id": 320, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 337, "output": { "id": 336, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 349, "output": { "id": 350, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 363, "output": { "id": 364, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 365, "output": { "id": 366, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 392, "output": { "id": 393, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 411, "output": { "id": 412, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 417, "output": { "id": 452, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 418, "output": { "id": 371, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 423, "output": { "id": 424, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 432, "output": { "id": 433, "damage": 0, "count": 1, "nbt": "" } }, { "type": 2, "inputId": 460, "output": { "id": 463, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 19, "inputDamage": 1, "output": { "id": 19, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 98, "inputDamage": 0, "output": { "id": 98, "damage": 2, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 0, "output": { "id": 220, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 1, "output": { "id": 221, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 2, "output": { "id": 222, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 3, "output": { "id": 223, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 4, "output": { "id": 224, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 5, "output": { "id": 225, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 6, "output": { "id": 226, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 7, "output": { "id": 227, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 8, "output": { "id": 228, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 9, "output": { "id": 229, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 10, "output": { "id": 219, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 11, "output": { "id": 231, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 12, "output": { "id": 232, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 13, "output": { "id": 233, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 14, "output": { "id": 234, "damage": 0, "count": 1, "nbt": "" } }, { "type": 3, "inputId": 159, "inputDamage": 15, "output": { "id": 235, "damage": 0, "count": 1, "nbt": "" } } ]# 全局资源包配置文件 # 编辑这个配置文件以在你的 GenisysPro 服务器使用全局资源包。 # 选择玩家是否需要接受全局资源包才能进入游戏。 force_resources: false # 资源包列表。此处添加的资源包应存放在 `resource_packs` 文件夹下。 resource_stack: # 示例 # - 苦力怕材质.mcpack # - 原版资源包.zip # Global resource pack configuration # Configure this file to use global resource packs on your GenisysPro server. # Choose whether players have to accept downloading your resource pack(s) to join the game. force_resources: false # List of your resource packs. They should be put under `resource_packs` folder. resource_stack: # e.g. # - Creeper_texture_pack.mcpack # - vanilla.zip # 全球資源包配置檔案 # 編輯這個配置檔以在你的 GenisysPro 伺服器上套用全球資源包。 # 決定玩家是否需要接受全球資源包才能進入游戲。 force_resources: false # 資源包列表。此處添加的資源包應存儲于 `resource_packs` 資料夾下。 resource_stack: # 示例 # - 爬行者材質.mcpack # - 原版資源包.zip server = $server; $this->size = (int) $size; for($i = 0; $i < $this->size; ++$i){ $this->workerUsage[$i] = 0; $this->workers[$i] = new AsyncWorker($this->server->getLogger(), $i + 1); $this->workers[$i]->setClassLoader($this->server->getLoader()); $this->workers[$i]->start(); } } /** * @return int */ public function getSize(){ return $this->size; } /** * @param $newSize */ public function increaseSize($newSize){ $newSize = (int) $newSize; if($newSize > $this->size){ for($i = $this->size; $i < $newSize; ++$i){ $this->workerUsage[$i] = 0; $this->workers[$i] = new AsyncWorker($this->server->getLogger(), $i + 1); $this->workers[$i]->setClassLoader($this->server->getLoader()); $this->workers[$i]->start(); } $this->size = $newSize; } } /** * @param AsyncTask $task * @param $worker */ public function submitTaskToWorker(AsyncTask $task, $worker){ if(isset($this->tasks[$task->getTaskId()]) or $task->isGarbage()){ return; } $worker = (int) $worker; if($worker < 0 or $worker >= $this->size){ throw new \InvalidArgumentException("Invalid worker $worker"); } $this->tasks[$task->getTaskId()] = $task; $this->workers[$worker]->stack($task); $this->workerUsage[$worker]++; $this->taskWorkers[$task->getTaskId()] = $worker; } /** * @param AsyncTask $task */ public function submitTask(AsyncTask $task){ if(isset($this->tasks[$task->getTaskId()]) or $task->isGarbage()){ return; } $selectedWorker = mt_rand(0, $this->size - 1); $selectedTasks = $this->workerUsage[$selectedWorker]; for($i = 0; $i < $this->size; ++$i){ if($this->workerUsage[$i] < $selectedTasks){ $selectedWorker = $i; $selectedTasks = $this->workerUsage[$i]; } } $this->submitTaskToWorker($task, $selectedWorker); } /** * @param AsyncTask $task * @param bool $force */ private function removeTask(AsyncTask $task, $force = false){ $task->setGarbage(); if(isset($this->taskWorkers[$task->getTaskId()])){ if(!$force and ($task->isRunning() or !$task->isGarbage())){ return; } $this->workerUsage[$this->taskWorkers[$task->getTaskId()]]--; $this->workers[$this->taskWorkers[$task->getTaskId()]]->collector($task); } unset($this->tasks[$task->getTaskId()]); unset($this->taskWorkers[$task->getTaskId()]); $task->cleanObject(); } public function removeTasks(){ do{ foreach($this->tasks as $task){ $task->cancelRun(); $this->removeTask($task); } if(count($this->tasks) > 0){ Server::microSleep(25000); } }while(count($this->tasks) > 0); for($i = 0; $i < $this->size; ++$i){ $this->workerUsage[$i] = 0; } $this->taskWorkers = []; $this->tasks = []; } public function collectTasks(){ Timings::$schedulerAsyncTimer->startTiming(); foreach($this->tasks as $task){ if($task->isFinished() and !$task->isRunning() and !$task->isCrashed()){ if(!$task->hasCancelledRun()){ $task->onCompletion($this->server); } $this->removeTask($task); }elseif($task->isTerminated() or $task->isCrashed()){ $this->server->getLogger()->critical("Could not execute asynchronous task " . (new \ReflectionClass($task))->getShortName() . ": Task crashed"); $this->removeTask($task, true); } } Timings::$schedulerAsyncTimer->stopTiming(); } } isGarbage; } public function setGarbage(){ $this->isGarbage = true; } /** * @return bool */ public function isFinished() : bool{ return $this->isFinished; } public function run(){ $this->result = null; $this->isGarbage = false; if($this->cancelRun !== true){ try{ $this->onRun(); }catch(\Throwable $e){ $this->crashed = true; $this->worker->handleException($e); } } $this->isFinished = true; //$this->setGarbage(); } /** * @return bool */ public function isCrashed(){ return $this->crashed; } /** * @return mixed */ public function getResult(){ return $this->serialized ? unserialize($this->result) : $this->result; } public function cancelRun(){ $this->cancelRun = true; } /** * @return bool */ public function hasCancelledRun(){ return $this->cancelRun === true; } /** * @return bool */ public function hasResult(){ return $this->result !== null; } /** * @param mixed $result * @param bool $serialize */ public function setResult($result, $serialize = true){ $this->result = $serialize ? serialize($result) : $result; $this->serialized = $serialize; } /** * @param $taskId */ public function setTaskId($taskId){ $this->taskId = $taskId; } /** * @return int */ public function getTaskId(){ return $this->taskId; } /** * Gets something into the local thread store. * You have to initialize this in some way from the task on run * * @param string $identifier * * @return mixed */ public function getFromThreadStore($identifier){ global $store; return $this->isGarbage() ? null : $store[$identifier]; } /** * Saves something into the local thread store. * This might get deleted at any moment. * * @param string $identifier * @param mixed $value */ public function saveToThreadStore($identifier, $value){ global $store; if(!$this->isGarbage()){ $store[$identifier] = $value; } } /** * Actions to execute when run * * @return void */ public abstract function onRun(); /** * Actions to execute when completed (on main thread) * Implement this if you want to handle the data in your AsyncTask after it has been processed * * @param Server $server * * @return void */ public function onCompletion(Server $server){ } public function cleanObject(){ foreach($this as $p => $v){ if(!($v instanceof \Threaded) and !in_array($p, ["isFinished", "isGarbage", "cancelRun"])){ $this->{$p} = null; } } } } logger = $logger; $this->id = $id; } public function run(){ $this->registerClassLoader(); gc_enable(); ini_set("memory_limit", -1); global $store; $store = []; } /** * @param \Throwable $e */ public function handleException(\Throwable $e){ $this->logger->logException($e); } /** * @return string */ public function getThreadName(){ return "Asynchronous Worker #" . $this->id; } } callable = $callable; $this->args = $args; $this->args[] = $this; } /** * @return callable */ public function getCallable(){ return $this->callable; } /** * @param $currentTicks */ public function onRun($currentTicks){ call_user_func_array($this->callable, $this->args); } } data = $data; $this->autotimes = $autotimes; } public function onRun(){ $re = [0, 0]; foreach($this->data as $d){ $data = $this->getInfo($d); $re[0] = $re[0] + $data[0]; $re[1] = $re[1] + $data[1]; } $this->re = (array) $re; } /** * @param $ds * @param int $time * * @return array */ public function getInfo($ds, $time = 1){ $tmp = explode(":", $ds); $ip = $tmp[0]; $port = $tmp[1]; $client = stream_socket_client("udp://" . $ip . ":" . $port, $errno, $errstr); //非阻塞Socket if($client){ stream_set_timeout($client, 1); $Handshake_to = "\xFE\xFD" . chr(9) . pack("N", 233); fwrite($client, $Handshake_to); $Handshake_re_1 = fread($client, 65535); if($Handshake_re_1 != ""){ $Handshake_re = $this->decode($Handshake_re_1); $Status_to = "\xFE\xFD" . chr(0) . pack("N", 233) . pack("N", $Handshake_re["payload"]); fwrite($client, $Status_to); $Status_re_1 = fread($client, 65535); if($Status_re_1 != ""){ $Status_re = $this->decode($Status_re_1); $ServerData = explode("\x00", $Status_re["payload"]); return [$ServerData[3], $ServerData[4]]; } } fclose($client); } if($time < $this->autotimes){ return $this->getInfo($ds, $time + 1); }elseif($time = $this->autotimes) return [0, 0]; return [0, 0]; } /** * @param Server $server */ public function onCompletion(Server $server){ $re = $this->re; if($re[0] > 0) $server->dserverPlayers = $re[0]; if($re[1] > 0) $server->dserverAllPlayers = $re[1]; //$server->getNetwork()->updateName(); } /** * @param $buffer * * @return array */ public function decode($buffer){ $redata = []; $redata["packetType"] = ord($buffer{0}); $redata["sessionID"] = unpack("N", substr($buffer, 1, 4))[1]; $redata["payload"] = rtrim(substr($buffer, 5)); return $redata; } }path = $path; $this->contents = $contents; $this->flags = (int) $flags; } public function onRun(){ try{ file_put_contents($this->path, $this->contents, (int) $this->flags); }catch(\Throwable $e){ } } } owner = $owner; } /** * @return Plugin */ public final function getOwner(){ return $this->owner; } } getProperty("anonymous-statistics.host", "stats.pocketmine.net") . "/"; $data = []; $data["uniqueServerId"] = $server->getServerUniqueId(); $data["uniqueMachineId"] = Utils::getMachineUniqueId(); $data["uniqueRequestId"] = UUID::fromData($server->getServerUniqueId(), microtime(true)); switch($type){ case self::TYPE_OPEN: $data["event"] = "open"; $version = new VersionString(); $data["server"] = [ "port" => $server->getPort(), "software" => $server->getName(), "fullVersion" => $version->get(true), "version" => $version->get(), "build" => $version->getBuild(), "api" => $server->getApiVersion(), "minecraftVersion" => $server->getVersion(), "protocol" => ProtocolInfo::CURRENT_PROTOCOL ]; $data["system"] = [ "operatingSystem" => Utils::getOS(), "cores" => Utils::getCoreCount(), "phpVersion" => PHP_VERSION, "machine" => php_uname("a"), "release" => php_uname("r"), "platform" => php_uname("i") ]; $data["players"] = [ "count" => 0, "limit" => $server->getMaxPlayers() ]; $plugins = []; foreach($server->getPluginManager()->getPlugins() as $p){ $d = $p->getDescription(); $plugins[$d->getName()] = [ "name" => $d->getName(), "version" => $d->getVersion(), "enabled" => $p->isEnabled() ]; } $data["plugins"] = $plugins; break; case self::TYPE_STATUS: $data["event"] = "status"; $data["server"] = [ "ticksPerSecond" => $server->getTicksPerSecondAverage(), "tickUsage" => $server->getTickUsageAverage(), "ticks" => $server->getTick() ]; //This anonymizes the user ids so they cannot be reversed to the original foreach($playerList as $k => $v){ $playerList[$k] = md5($v); } $players = []; foreach($server->getOnlinePlayers() as $p){ if($p->isOnline()){ $players[] = md5($p->getUniqueId()->toBinary()); } } $data["players"] = [ "count" => count($players), "limit" => $server->getMaxPlayers(), "currentList" => $players, "historyList" => array_values($playerList) ]; $info = Utils::getMemoryUsage(true); $data["system"] = [ "mainMemory" => $info[0], "totalMemory" => $info[1], "availableMemory" => $info[2], "threadCount" => Utils::getThreadCount() ]; break; case self::TYPE_CLOSE: $data["event"] = "close"; $data["crashing"] = $server->isRunning(); break; } $this->endpoint = $endpoint . "api/post"; $this->data = json_encode($data/*, JSON_PRETTY_PRINT*/); } public function onRun(){ try{ Utils::postURL($this->endpoint, $this->data, 5, [ "Content-Type: application/json", "Content-Length: " . strlen($this->data) ]); }catch(\Throwable $e){ } } } */ protected $queue; /** * @var TaskHandler[] */ protected $tasks = []; /** @var AsyncPool */ protected $asyncPool; /** @var int */ private $ids = 1; /** @var int */ protected $currentTick = 0; /** * ServerScheduler constructor. */ public function __construct(){ $this->queue = new ReversePriorityQueue(); $this->asyncPool = new AsyncPool(Server::getInstance(), self::$WORKERS); } /** * @param Task $task * * @return null|TaskHandler */ public function scheduleTask(Task $task){ return $this->addTask($task, -1, -1); } /** * Submits an asynchronous task to the Worker Pool * * @param AsyncTask $task * * @return void */ public function scheduleAsyncTask(AsyncTask $task){ $id = $this->nextId(); $task->setTaskId($id); $this->asyncPool->submitTask($task); } /** * Submits an asynchronous task to a specific Worker in the Pool * * @param AsyncTask $task * @param int $worker * * @return void */ public function scheduleAsyncTaskToWorker(AsyncTask $task, $worker){ $id = $this->nextId(); $task->setTaskId($id); $this->asyncPool->submitTaskToWorker($task, $worker); } /** * @return int */ public function getAsyncTaskPoolSize(){ return $this->asyncPool->getSize(); } /** * @param $newSize */ public function increaseAsyncTaskPoolSize($newSize){ $this->asyncPool->increaseSize($newSize); } /** * @param Task $task * @param int $delay * * @return null|TaskHandler */ public function scheduleDelayedTask(Task $task, $delay){ return $this->addTask($task, (int) $delay, -1); } /** * @param Task $task * @param int $period * * @return null|TaskHandler */ public function scheduleRepeatingTask(Task $task, $period){ return $this->addTask($task, -1, (int) $period); } /** * @param Task $task * @param int $delay * @param int $period * * @return null|TaskHandler */ public function scheduleDelayedRepeatingTask(Task $task, $delay, $period){ return $this->addTask($task, (int) $delay, (int) $period); } /** * @param int $taskId */ public function cancelTask($taskId){ if($taskId !== null and isset($this->tasks[$taskId])){ $this->tasks[$taskId]->cancel(); unset($this->tasks[$taskId]); } } /** * @param Plugin $plugin */ public function cancelTasks(Plugin $plugin){ foreach($this->tasks as $taskId => $task){ $ptask = $task->getTask(); if($ptask instanceof PluginTask and $ptask->getOwner() === $plugin){ $task->cancel(); unset($this->tasks[$taskId]); } } } public function cancelAllTasks(){ foreach($this->tasks as $task){ $task->cancel(); } $this->tasks = []; $this->asyncPool->removeTasks(); while(!$this->queue->isEmpty()){ $this->queue->extract(); } $this->ids = 1; } /** * @param int $taskId * * @return bool */ public function isQueued($taskId){ return isset($this->tasks[$taskId]); } /** * @param Task $task * @param $delay * @param $period * * @return null|TaskHandler * * @throws PluginException */ private function addTask(Task $task, $delay, $period){ if($task instanceof PluginTask){ if(!($task->getOwner() instanceof Plugin)){ throw new PluginException("Invalid owner of PluginTask " . get_class($task)); }elseif(!$task->getOwner()->isEnabled()){ throw new PluginException("Plugin '" . $task->getOwner()->getName() . "' attempted to register a task while disabled"); } } if($delay <= 0){ $delay = -1; } if($period <= -1){ $period = -1; }elseif($period < 1){ $period = 1; } return $this->handle(new TaskHandler(get_class($task), $task, $this->nextId(), $delay, $period)); } /** * @param TaskHandler $handler * * @return TaskHandler */ private function handle(TaskHandler $handler){ if($handler->isDelayed()){ $nextRun = $this->currentTick + $handler->getDelay(); }else{ $nextRun = $this->currentTick; } $handler->setNextRun($nextRun); $this->tasks[$handler->getTaskId()] = $handler; $this->queue->insert($handler, $nextRun); return $handler; } /** * @param int $currentTick */ public function mainThreadHeartbeat($currentTick){ $this->currentTick = $currentTick; while($this->isReady($this->currentTick)){ /** @var TaskHandler $task */ $task = $this->queue->extract(); if($task->isCancelled()){ unset($this->tasks[$task->getTaskId()]); continue; }else{ $task->timings->startTiming(); try{ $task->run($this->currentTick); }catch(\Throwable $e){ Server::getInstance()->getLogger()->critical("Could not execute task " . $task->getTaskName() . ": " . $e->getMessage()); Server::getInstance()->getLogger()->logException($e); } $task->timings->stopTiming(); } if($task->isRepeating()){ $task->setNextRun($this->currentTick + $task->getPeriod()); $this->queue->insert($task, $this->currentTick + $task->getPeriod()); }else{ $task->remove(); unset($this->tasks[$task->getTaskId()]); } } $this->asyncPool->collectTasks(); } /** * @param $currentTicks * * @return bool */ private function isReady($currentTicks){ return count($this->tasks) > 0 and $this->queue->current()->getNextRun() <= $currentTicks; } /** * @return int */ private function nextId(){ return $this->ids++; } } taskHandler; } /** * @return int */ public final function getTaskId(){ if($this->taskHandler !== null){ return $this->taskHandler->getTaskId(); } return -1; } /** * @param TaskHandler $taskHandler */ public final function setHandler($taskHandler){ if($this->taskHandler === null or $taskHandler === null){ $this->taskHandler = $taskHandler; } } /** * Actions to execute when run * * @param $currentTick * * @return void */ public abstract function onRun($currentTick); /** * Actions to execute if the Task is cancelled */ public function onCancel(){ } } task = $task; $this->taskId = $taskId; $this->delay = $delay; $this->period = $period; $this->timingName = $timingName === null ? "Unknown" : $timingName; $this->timings = Timings::getPluginTaskTimings($this, $period); $this->task->setHandler($this); } /** * @return bool */ public function isCancelled(){ return $this->cancelled === true; } /** * @return int */ public function getNextRun(){ return $this->nextRun; } /** * @param int $ticks */ public function setNextRun($ticks){ $this->nextRun = $ticks; } /** * @return int */ public function getTaskId(){ return $this->taskId; } /** * @return Task */ public function getTask(){ return $this->task; } /** * @return int */ public function getDelay(){ return $this->delay; } /** * @return bool */ public function isDelayed(){ return $this->delay > 0; } /** * @return bool */ public function isRepeating(){ return $this->period > 0; } /** * @return int */ public function getPeriod(){ return $this->period; } /** * WARNING: Do not use this, it's only for internal use. * Changes to this function won't be recorded on the version. */ public function cancel(){ if(!$this->isCancelled()){ $this->task->onCancel(); } $this->remove(); } public function remove(){ $this->cancelled = true; $this->task->setHandler(null); } /** * @param int $currentTick */ public function run($currentTick){ $this->task->onRun($currentTick); } /** * @return string */ public function getTaskName(){ if($this->timingName !== null){ return $this->timingName; } return get_class($this->task); } } primary)){ $nbt->primary = new IntTag("primary", 0); } if(!isset($nbt->secondary)){ $nbt->secondary = new IntTag("secondary", 0); } $this->inventory = new BeaconInventory($this); parent::__construct($level, $nbt); $this->scheduleUpdate(); } public function saveNBT(){ parent::saveNBT(); } /** * @return CompoundTag */ public function getSpawnCompound(){ $c = new CompoundTag("", [ new StringTag("id", Tile::BEACON), new ByteTag("isMovable", (bool) true), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), new IntTag("primary", $this->namedtag["primary"]), new IntTag("secondary", $this->namedtag["secondary"]) ]); if($this->hasName()){ $c->CustomName = $this->namedtag->CustomName; } return $c; } /** * @return string */ public function getName() : string{ return $this->hasName() ? $this->namedtag->CustomName->getValue() : "Beacon"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } /** * @return BeaconInventory */ public function getInventory(){ return $this->inventory; } /** * @param CompoundTag $nbt * @param Player $player * * @return bool */ public function updateCompoundTag(CompoundTag $nbt, Player $player) : bool{ if($nbt["id"] !== Tile::BEACON){ return false; } $this->namedtag->primary = new IntTag("primary", $nbt["primary"]); $this->namedtag->secondary = new IntTag("secondary", $nbt["secondary"]); return true; } /** * @return bool */ public function onUpdate(){ if($this->closed === true){ return false; } if($this->currentTick++ % 100 != 0){ return true; } $level = $this->calculatePowerLevel(); $this->timings->startTiming(); $id = 0; if($level > 0){ if(isset($this->namedtag->secondary) && $this->namedtag["primary"] != 0){ $id = $this->namedtag["primary"]; }else if(isset($this->namedtag->secondary) && $this->namedtag["secondary"] != 0){ $id = $this->namedtag["secondary"]; } if($id != 0){ $range = ($level + 1) * 10; $effect = Effect::getEffect($id); $effect->setDuration(10 * 30); $effect->setAmplifier(0); foreach($this->level->getPlayers() as $player){ if($this->distance($player) <= $range){ $player->addEffect($effect); } } } } $this->lastUpdate = microtime(true); $this->timings->stopTiming(); return true; } /** * @return int */ protected function calculatePowerLevel(){ $tileX = $this->getFloorX(); $tileY = $this->getFloorY(); $tileZ = $this->getFloorZ(); for($powerLevel = 1; $powerLevel <= self::POWER_LEVEL_MAX; $powerLevel++){ $queryY = $tileY - $powerLevel; for($queryX = $tileX - $powerLevel; $queryX <= $tileX + $powerLevel; $queryX++){ for($queryZ = $tileZ - $powerLevel; $queryZ <= $tileZ + $powerLevel; $queryZ++){ $testBlockId = $this->level->getBlockIdAt($queryX, $queryY, $queryZ); if( $testBlockId != Block::IRON_BLOCK && $testBlockId != Block::GOLD_BLOCK && $testBlockId != Block::EMERALD_BLOCK && $testBlockId != Block::DIAMOND_BLOCK ){ return $powerLevel - 1; } } } } return self::POWER_LEVEL_MAX; } } color) or !($nbt->color instanceof ByteTag)){ $nbt->color = new ByteTag("color", 14); //default to old red } parent::__construct($level, $nbt); } /** * @return int */ public function getColor() : int{ return $this->namedtag->color->getValue(); } /** * @param int $color */ public function setColor(int $color){ $this->namedtag["color"] = $color & 0x0f; $this->onChanged(); } /** * @return CompoundTag */ public function getSpawnCompound(){ return new CompoundTag("", [ new StringTag("id", Tile::BED), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), $this->namedtag->color ]); } } 0, Item::GLOWSTONE_DUST => 0, Item::REDSTONE => 0, Item::FERMENTED_SPIDER_EYE => 0, Item::MAGMA_CREAM => 0, Item::SUGAR => 0, Item::GLISTERING_MELON => 0, Item::SPIDER_EYE => 0, Item::GHAST_TEAR => 0, Item::BLAZE_POWDER => 0, Item::GOLDEN_CARROT => 0, //Item::RAW_FISH => Fish::FISH_PUFFERFISH, Item::PUFFER_FISH, Item::RABBIT_FOOT => 0, Item::GUNPOWDER => 0, ]; /** * BrewingStand constructor. * * @param Level $level * @param CompoundTag $nbt */ public function __construct(Level $level, CompoundTag $nbt){ if(!isset($nbt->CookedTime) or !($nbt->CookedTime instanceof ShortTag)){ $nbt->CookedTime = new ShortTag("CookedTime", 0); } parent::__construct($level, $nbt); $this->inventory = new BrewingInventory($this); if(!isset($this->namedtag->Items) or !($this->namedtag->Items instanceof ListTag)){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); } for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); } /*if($this->namedtag["CookTime"] < self::MAX_BREW_TIME){ $this->scheduleUpdate(); }*/ } /** * @return string */ public function getName() : string{ return $this->hasName() ? $this->namedtag->CustomName->getValue() : "Brewing Stand"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } public function close(){ if(!$this->closed){ foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getInventory()); } parent::close(); } } public function saveNBT(){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); for($index = 0; $index < $this->getSize(); ++$index){ $this->setItem($index, $this->inventory->getItem($index)); } } /** * @return int */ public function getSize(){ return 4; } /** * @param $index * * @return int */ protected function getSlotIndex($index){ foreach($this->namedtag->Items as $i => $slot){ if($slot["Slot"] === $index){ return $i; } } return -1; } /** * This method should not be used by plugins, use the Inventory * * @param int $index * * @return Item */ public function getItem($index){ $i = $this->getSlotIndex($index); if($i < 0){ return Item::get(Item::AIR, 0, 0); }else{ return Item::nbtDeserialize($this->namedtag->Items[$i]); } } /** * This method should not be used by plugins, use the Inventory * * @param int $index * @param Item $item * * @return bool */ public function setItem($index, Item $item){ $i = $this->getSlotIndex($index); if($item->getId() === Item::AIR or $item->getCount() <= 0){ if($i >= 0){ unset($this->namedtag->Items[$i]); } }elseif($i < 0){ for($i = 0; $i <= $this->getSize(); ++$i){ if(!isset($this->namedtag->Items[$i])){ break; } } $this->namedtag->Items[$i] = $item->nbtSerialize($index); }else{ $this->namedtag->Items[$i] = $item->nbtSerialize($index); } return true; } /** * @return BrewingInventory */ public function getInventory(){ return $this->inventory; } /** * @param Item $item * * @return bool */ public function checkIngredient(Item $item){ if(isset(self::$ingredients[$item->getId()])){ if(self::$ingredients[$item->getId()] === $item->getDamage()){ return true; } } return false; } public function updateSurface(){ $this->saveNBT(); $this->onChanged(); } /** * @return bool */ public function onUpdate(){ if($this->closed === true){ return false; } $this->timings->startTiming(); $ret = false; $ingredient = $this->inventory->getIngredient(); $canBrew = false; for($i = 1; $i <= 3; $i++){ if($this->inventory->getItem($i)->getId() === Item::POTION or $this->inventory->getItem($i)->getId() === Item::SPLASH_POTION ){ $canBrew = true; } } if($ingredient->getId() !== Item::AIR and $ingredient->getCount() > 0){ if($canBrew){ if(!$this->checkIngredient($ingredient)){ $canBrew = false; } } if($canBrew){ for($i = 1; $i <= 3; $i++){ $potion = $this->inventory->getItem($i); $recipe = Server::getInstance()->getCraftingManager()->matchBrewingRecipe($ingredient, $potion); if($recipe !== null){ $canBrew = true; break; } $canBrew = false; } } }else{ $canBrew = false; } if($canBrew){ $this->namedtag->CookTime = new ShortTag("CookTime", $this->namedtag["CookTime"] - 1); foreach($this->getInventory()->getViewers() as $player){ $windowId = $player->getWindowId($this->getInventory()); if($windowId > 0){ $pk = new ContainerSetDataPacket(); $pk->windowid = $windowId; $pk->property = 0; //Brew $pk->value = $this->namedtag["CookTime"]; $player->dataPacket($pk); } } if($this->namedtag["CookTime"] <= 0){ $this->namedtag->CookTime = new ShortTag("CookTime", self::MAX_BREW_TIME); for($i = 1; $i <= 3; $i++){ $potion = $this->inventory->getItem($i); $recipe = Server::getInstance()->getCraftingManager()->matchBrewingRecipe($ingredient, $potion); if($recipe != null and $potion->getId() !== Item::AIR){ $this->inventory->setItem($i, $recipe->getResult()); } } $ingredient->count--; if($ingredient->getCount() <= 0) $ingredient = Item::get(Item::AIR); $this->inventory->setIngredient($ingredient); } $ret = true; }else{ $this->namedtag->CookTime = new ShortTag("CookTime", self::MAX_BREW_TIME); foreach($this->getInventory()->getViewers() as $player){ $windowId = $player->getWindowId($this->getInventory()); if($windowId > 0){ $pk = new ContainerSetDataPacket(); $pk->windowid = $windowId; $pk->property = 0; //Brew $pk->value = 0; $player->dataPacket($pk); } } } $this->lastUpdate = microtime(true); $this->timings->stopTiming(); return $ret; } /** * @return CompoundTag */ public function getSpawnCompound(){ $nbt = new CompoundTag("", [ new StringTag("id", Tile::BREWING_STAND), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), new ShortTag("CookTime", self::MAX_BREW_TIME), $this->namedtag->Items, ]); if($this->hasName()){ $nbt->CustomName = $this->namedtag->CustomName; } return $nbt; } } PotionId) or !($nbt->PotionId instanceof ShortTag)){ $nbt->PotionId = new ShortTag("PotionId", 0xffff); } if(!isset($nbt->SplashPotion) or !($nbt->SplashPotion instanceof ByteTag)){ $nbt->SplashPotion = new ByteTag("SplashPotion", 0); } if(!isset($nbt->Items) or !($nbt->Items instanceof ListTag)){ $nbt->Items = new ListTag("Items", []); } parent::__construct($level, $nbt); } /** * @return mixed|null */ public function getPotionId(){ return $this->namedtag["PotionId"]; } /** * @param $potionId */ public function setPotionId($potionId){ $this->namedtag->PotionId = new ShortTag("PotionId", $potionId); $this->onChanged(); } /** * @return bool */ public function hasPotion(){ return $this->namedtag["PotionId"] !== 0xffff; } /** * @return bool */ public function getSplashPotion(){ return ($this->namedtag["SplashPotion"] == true); } /** * @param $bool */ public function setSplashPotion($bool){ $this->namedtag->SplashPotion = new ByteTag("SplashPotion", ($bool == true) ? 1 : 0); $this->onChanged(); } /** * @return null|Color */ public function getCustomColor(){// if($this->isCustomColor()){ $color = $this->namedtag["CustomColor"]; $green = ($color >> 8) & 0xff; $red = ($color >> 16) & 0xff; $blue = ($color) & 0xff; return Color::getRGB($red, $green, $blue); } return null; } /** * @return int */ public function getCustomColorRed(){ return ($this->namedtag["CustomColor"] >> 16) & 0xff; } /** * @return int */ public function getCustomColorGreen(){ return ($this->namedtag["CustomColor"] >> 8) & 0xff; } /** * @return int */ public function getCustomColorBlue(){ return ($this->namedtag["CustomColor"]) & 0xff; } /** * @return bool */ public function isCustomColor(){ return isset($this->namedtag->CustomColor); } /** * @param $r * @param int $g * @param int $b */ public function setCustomColor($r, $g = 0xff, $b = 0xff){ if($r instanceof Color){ $color = ($r->getRed() << 16 | $r->getGreen() << 8 | $r->getBlue()) & 0xffffff; }else{ $color = ($r << 16 | $g << 8 | $b) & 0xffffff; } $this->namedtag->CustomColor = new IntTag("CustomColor", $color); $this->onChanged(); } public function clearCustomColor(){ if(isset($this->namedtag->CustomColor)){ unset($this->namedtag->CustomColor); } $this->onChanged(); } /** * @return CompoundTag */ public function getSpawnCompound(){ $nbt = new CompoundTag("", [ new StringTag("id", Tile::CAULDRON), new IntTag("x", (Int) $this->x), new IntTag("y", (Int) $this->y), new IntTag("z", (Int) $this->z), new ShortTag("PotionId", $this->namedtag["PotionId"]), new ByteTag("SplashPotion", $this->namedtag["SplashPotion"]), new ListTag("Items", $this->namedtag["Items"])//unused? ]); if($this->getPotionId() === 0xffff and $this->isCustomColor()){ $nbt->CustomColor = $this->namedtag->CustomColor; } return $nbt; } } inventory = new ChestInventory($this); if(!isset($this->namedtag->Items) or !($this->namedtag->Items instanceof ListTag)){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); } for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); } } public function close(){ if($this->closed === false){ foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getInventory()); } foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getRealInventory()); } parent::close(); } } public function saveNBT(){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); for($index = 0; $index < $this->getSize(); ++$index){ $this->setItem($index, $this->inventory->getItem($index)); } } /** * @return int */ public function getSize(){ return 27; } /** * @param $index * * @return int */ protected function getSlotIndex($index){ foreach($this->namedtag->Items as $i => $slot){ if((int) $slot["Slot"] === (int) $index){ return (int) $i; } } return -1; } /** * This method should not be used by plugins, use the Inventory * * @param int $index * * @return Item */ public function getItem($index){ $i = $this->getSlotIndex($index); if($i < 0){ return Item::get(Item::AIR, 0, 0); }else{ return Item::nbtDeserialize($this->namedtag->Items[$i]); } } /** * This method should not be used by plugins, use the Inventory * * @param int $index * @param Item $item * * @return bool */ public function setItem($index, Item $item){ $i = $this->getSlotIndex($index); if($item->getId() === Item::AIR or $item->getCount() <= 0){ if($i >= 0){ unset($this->namedtag->Items[$i]); } }elseif($i < 0){ for($i = 0; $i <= $this->getSize(); ++$i){ if(!isset($this->namedtag->Items[$i])){ break; } } $this->namedtag->Items[$i] = $item->nbtSerialize($index); }else{ $this->namedtag->Items[$i] = $item->nbtSerialize($index); } return true; } /** * @return ChestInventory|DoubleChestInventory */ public function getInventory(){ if($this->isPaired() and $this->doubleInventory === null){ $this->checkPairing(); } return $this->doubleInventory instanceof DoubleChestInventory ? $this->doubleInventory : $this->inventory; } /** * @return ChestInventory */ public function getRealInventory(){ return $this->inventory; } /** * @return DoubleChestInventory|null */ public function getDoubleInventory(){ return $this->doubleInventory; } protected function checkPairing(){ if($this->isPaired() and !$this->getLevel()->isChunkLoaded($this->namedtag->pairx->getValue() >> 4, $this->namedtag->pairz->getValue() >> 4)){ //paired to a tile in an unloaded chunk $this->doubleInventory = null; }elseif(($pair = $this->getPair()) instanceof Chest){ if(!$pair->isPaired()){ $pair->createPair($this); $pair->checkPairing(); } if($this->doubleInventory === null){ if(($p = $pair->getDoubleInventory()) instanceof DoubleChestInventory){ $this->doubleInventory = $p; }else{ if(($pair->x + ($pair->z << 15)) > ($this->x + ($this->z << 15))){ //Order them correctly $this->doubleInventory = new DoubleChestInventory($pair, $this); }else{ $this->doubleInventory = new DoubleChestInventory($this, $pair); } } } }else{ $this->doubleInventory = null; unset($this->namedtag->pairx, $this->namedtag->pairz); } } /** * @return string */ public function getName() : string{ return isset($this->namedtag->CustomName) ? $this->namedtag->CustomName->getValue() : "Chest"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } /** * @return bool */ public function isPaired(){ if(!isset($this->namedtag->pairx) or !isset($this->namedtag->pairz)){ return false; } return true; } /** * @return Chest */ public function getPair(){ if($this->isPaired()){ $tile = $this->getLevel()->getTile(new Vector3((int) $this->namedtag["pairx"], $this->y, (int) $this->namedtag["pairz"])); if($tile instanceof Chest){ return $tile; } } return null; } /** * @param Chest $tile * * @return bool */ public function pairWith(Chest $tile){ if($this->isPaired() or $tile->isPaired()){ return false; } $this->createPair($tile); $this->spawnToAll(); $tile->spawnToAll(); $this->checkPairing(); return true; } /** * @param Chest $tile */ private function createPair(Chest $tile){ $this->namedtag->pairx = new IntTag("pairx", $tile->x); $this->namedtag->pairz = new IntTag("pairz", $tile->z); $tile->namedtag->pairx = new IntTag("pairx", $this->x); $tile->namedtag->pairz = new IntTag("pairz", $this->z); } /** * @return bool */ public function unpair(){ if(!$this->isPaired()){ return false; } $tile = $this->getPair(); unset($this->namedtag->pairx, $this->namedtag->pairz); $this->spawnToAll(); if($tile instanceof Chest){ unset($tile->namedtag->pairx, $tile->namedtag->pairz); $tile->checkPairing(); $tile->spawnToAll(); } $this->checkPairing(); return true; } /** * @return CompoundTag */ public function getSpawnCompound(){ if($this->isPaired()){ $c = new CompoundTag("", [ new StringTag("id", Tile::CHEST), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), new IntTag("pairx", (int) $this->namedtag["pairx"]), new IntTag("pairz", (int) $this->namedtag["pairz"]) ]); }else{ $c = new CompoundTag("", [ new StringTag("id", Tile::CHEST), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); } if($this->hasName()){ $c->CustomName = $this->namedtag->CustomName; } return $c; } } scheduleUpdate(); } /** * @return int */ public function getLightByTime(){ /* $strength = 1; $time = $this->getLevel()->getTime(); if(WeatherManager::isRegistered($this->getLevel())) $weather = $this->getLevel()->getWeather()->getWeather(); else $weather = Weather::SUNNY; switch($weather){ case Weather::SUNNY: if($time <= 22340 and $time >= 13680) $strength = 1; if($time <= 22800 and $time >= 13220) $strength = 2; if($time <= 23080 and $time >= 12940) $strength = 3; if($time <= 23300 and $time >= 12720) $strength = 4; if($time <= 23540 and $time >= 12480) $strength = 5; if($time <= 23780 and $time >= 12240) $strength = 6; if($time <= 23960 and $time >= 12040) $strength = 7; if($time >= 180 and $time <= 11840) $strength = 8; if($time >= 540 and $time <= 11480) $strength = 9; if($time >= 940 and $time <= 11080) $strength = 10; if($time >= 1380 and $time <= 10640) $strength = 11; if($time >= 1880 and $time <= 10140) $strength = 12; if($time >= 2460 and $time <= 9560) $strength = 13; if($time >= 3180 and $time <= 8840) $strength = 14; if($time >= 4300 and $time <= 7720) $strength = 15; break; case Weather::RAINY_THUNDER: case Weather::RAINY: if($time <= 22340 and $time >= 13680) $strength = 1; if($time <= 22800 and $time >= 13220) $strength = 2; if($time <= 23240 and $time >= 12780) $strength = 3; if($time <= 23520 and $time >= 12500) $strength = 4; if($time <= 23760 and $time >= 12260) $strength = 5; if($time >= 0 and $time <= 12020) $strength = 6; if($time >= 400 and $time <= 11620) $strength = 7; if($time >= 900 and $time <= 11120) $strength = 8; if($time >= 1440 and $time <= 10580) $strength = 9; if($time >= 2080 and $time <= 9940) $strength = 10; if($time >= 2880 and $time <= 9140) $strength = 11; if($time >= 4120 and $time <= 7990) $strength = 12; break; } return $strength;*/ $time = $this->getLevel()->getTime(); if(($time >= Level::TIME_DAY and $time <= Level::TIME_SUNSET) or ($time >= Level::TIME_SUNRISE and $time <= Level::TIME_FULL) ) return 15; return 0; } /** * @return bool */ public function isActivated() : bool{ if($this->getType() == Block::DAYLIGHT_SENSOR){ if($this->getLightByTime() == 15) return true; return false; }else{ if($this->getLightByTime() == 0) return true; return false; } } /** * @return int */ private function getType() : int{ return $this->getBlock()->getId(); } /** * @return bool */ public function onUpdate(){ if(($this->getLevel()->getServer()->getTick() % 3) == 0){ //Update per 3 ticks if($this->getType() != $this->lastType){ //Update when changed /** @var DaylightDetector $block */ $block = $this->getBlock(); if($this->isActivated()){ $block->activate(); }else{ $block->deactivate(); } $this->lastType = $block->getId(); } } return true; } /** * @return CompoundTag */ public function getSpawnCompound(){ return new CompoundTag("", [ new StringTag("id", Tile::DAY_LIGHT_DETECTOR), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), ]); } }inventory = new DispenserInventory($this); if(!isset($this->namedtag->Items) or !($this->namedtag->Items instanceof ListTag)){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); } for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); } $this->scheduleUpdate(); } public function close(){ if($this->closed === false){ foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getInventory()); } parent::close(); } } public function saveNBT(){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); for($index = 0; $index < $this->getSize(); ++$index){ $this->setItem($index, $this->inventory->getItem($index)); } } /** * @return int */ public function getSize(){ return 9; } /** * @param $index * * @return int */ protected function getSlotIndex($index){ foreach($this->namedtag->Items as $i => $slot){ if((int) $slot["Slot"] === (int) $index){ return (int) $i; } } return -1; } /** * This method should not be used by plugins, use the Inventory * * @param int $index * * @return Item */ public function getItem($index){ $i = $this->getSlotIndex($index); if($i < 0){ return Item::get(Item::AIR, 0, 0); }else{ return Item::nbtDeserialize($this->namedtag->Items[$i]); } } /** * This method should not be used by plugins, use the Inventory * * @param int $index * @param Item $item * * @return bool */ public function setItem($index, Item $item){ $i = $this->getSlotIndex($index); if($item->getId() === Item::AIR or $item->getCount() <= 0){ if($i >= 0){ unset($this->namedtag->Items[$i]); } }elseif($i < 0){ for($i = 0; $i <= $this->getSize(); ++$i){ if(!isset($this->namedtag->Items[$i])){ break; } } $this->namedtag->Items[$i] = $item->nbtSerialize($index); }else{ $this->namedtag->Items[$i] = $item->nbtSerialize($index); } return true; } /** * @return DispenserInventory */ public function getInventory(){ return $this->inventory; } /** * @return string */ public function getName() : string{ return isset($this->namedtag->CustomName) ? $this->namedtag->CustomName->getValue() : "Dispenser"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } /** * @return array */ public function getMotion(){ $meta = $this->getBlock()->getDamage(); switch($meta){ case Vector3::SIDE_DOWN: return [0, -1, 0]; case Vector3::SIDE_UP: return [0, 1, 0]; case Vector3::SIDE_NORTH: return [0, 0, -1]; case Vector3::SIDE_SOUTH: return [0, 0, 1]; case Vector3::SIDE_WEST: return [-1, 0, 0]; case Vector3::SIDE_EAST: return [1, 0, 0]; default: return [0, 0, 0]; } } public function activate(){ $itemIndex = []; for($i = 0; $i < $this->getSize(); $i++){ $item = $this->getInventory()->getItem($i); if($item->getId() != Item::AIR){ $itemIndex[] = [$i, $item]; } } $max = count($itemIndex) - 1; if($max < 0) $itemArr = null; elseif($max == 0) $itemArr = $itemIndex[0]; else $itemArr = $itemIndex[mt_rand(0, $max)]; if(is_array($itemArr)){ /** @var Item $item */ $item = $itemArr[1]; $item->setCount($item->getCount() - 1); $this->getInventory()->setItem($itemArr[0], $item->getCount() > 0 ? $item : Item::get(Item::AIR)); $motion = $this->getMotion(); $needItem = Item::get($item->getId(), $item->getDamage()); $f = 1.5; $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x + $motion[0] * 2 + 0.5), new DoubleTag("", $this->y + ($motion[1] > 0 ? $motion[1] : 0.5)), new DoubleTag("", $this->z + $motion[2] * 2 + 0.5) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", $motion[0]), new DoubleTag("", $motion[1]), new DoubleTag("", $motion[2]) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", lcg_value() * 360), new FloatTag("", 0) ]), ]); switch($needItem->getId()){ case Item::ARROW: $nbt->Fire = new ShortTag("Fire", 0); $entity = Entity::createEntity("Arrow", $this->getLevel(), $nbt); break; case Item::SNOWBALL: $entity = Entity::createEntity("Snowball", $this->getLevel(), $nbt); break; case Item::EGG: $entity = Entity::createEntity("Egg", $this->getLevel(), $nbt); break; case Item::SPLASH_POTION: $nbt->PotionId = new ShortTag("PotionId", $item->getDamage()); $entity = Entity::createEntity("ThrownPotion", $this->getLevel(), $nbt); break; case Item::ENCHANTING_BOTTLE: $entity = Entity::createEntity("ThrownExpBottle", $this->getLevel(), $nbt); break; default: $nbt->Health = new ShortTag("Health", 5); $nbt->Item = $needItem->nbtSerialize(-1, "Item"); $nbt->PickupDelay = new ShortTag("PickupDelay", 10); $f = 0.3; $entity = new ItemEntity($this->getLevel(), $nbt, $this); break; } $entity->setMotion($entity->getMotion()->multiply($f)); $entity->spawnToAll(); for($i = 1; $i < 10; $i++){ $this->getLevel()->addParticle(new SmokeParticle($this->add($motion[0] * $i * 0.3 + 0.5, $motion[1] == 0 ? 0.5 : $motion[1] * $i * 0.3, $motion[2] * $i * 0.3 + 0.5))); } } } /** * @return CompoundTag */ public function getSpawnCompound(){ $c = new CompoundTag("", [ new StringTag("id", Tile::DISPENSER), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); if($this->hasName()){ $c->CustomName = $this->namedtag->CustomName; } return $c; } }inventory = new DropperInventory($this); if(!isset($this->namedtag->Items) or !($this->namedtag->Items instanceof ListTag)){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); } for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); } $this->scheduleUpdate(); } public function close(){ if($this->closed === false){ foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getInventory()); } foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getInventory()); } parent::close(); } } public function saveNBT(){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); for($index = 0; $index < $this->getSize(); ++$index){ $this->setItem($index, $this->inventory->getItem($index)); } } /** * @return int */ public function getSize(){ return 9; } /** * @param $index * * @return int */ protected function getSlotIndex($index){ foreach($this->namedtag->Items as $i => $slot){ if((int) $slot["Slot"] === (int) $index){ return (int) $i; } } return -1; } /** * This method should not be used by plugins, use the Inventory * * @param int $index * * @return Item */ public function getItem($index){ $i = $this->getSlotIndex($index); if($i < 0){ return Item::get(Item::AIR, 0, 0); }else{ return Item::nbtDeserialize($this->namedtag->Items[$i]); } } /** * This method should not be used by plugins, use the Inventory * * @param int $index * @param Item $item * * @return bool */ public function setItem($index, Item $item){ $i = $this->getSlotIndex($index); if($item->getId() === Item::AIR or $item->getCount() <= 0){ if($i >= 0){ unset($this->namedtag->Items[$i]); } }elseif($i < 0){ for($i = 0; $i <= $this->getSize(); ++$i){ if(!isset($this->namedtag->Items[$i])){ break; } } $this->namedtag->Items[$i] = $item->nbtSerialize($index); }else{ $this->namedtag->Items[$i] = $item->nbtSerialize($index); } return true; } /** * @return DropperInventory */ public function getInventory(){ return $this->inventory; } /** * @return string */ public function getName() : string{ return isset($this->namedtag->CustomName) ? $this->namedtag->CustomName->getValue() : "Dropper"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } /** * @return array */ public function getMotion(){ $meta = $this->getBlock()->getDamage(); switch($meta){ case Vector3::SIDE_DOWN: return [0, -1, 0]; case Vector3::SIDE_UP: return [0, 1, 0]; case Vector3::SIDE_NORTH: return [0, 0, -1]; case Vector3::SIDE_SOUTH: return [0, 0, 1]; case Vector3::SIDE_WEST: return [-1, 0, 0]; case Vector3::SIDE_EAST: return [1, 0, 0]; default: return [0, 0, 0]; } } public function activate(){ $itemIndex = []; for($i = 0; $i < $this->getSize(); $i++){ $item = $this->getInventory()->getItem($i); if($item->getId() != Item::AIR){ $itemIndex[] = [$i, $item]; } } $max = count($itemIndex) - 1; if($max < 0) $itemArr = null; elseif($max == 0) $itemArr = $itemIndex[0]; else $itemArr = $itemIndex[mt_rand(0, $max)]; if(is_array($itemArr)){ /** @var Item $item */ $item = $itemArr[1]; $item->setCount($item->getCount() - 1); $this->getInventory()->setItem($itemArr[0], $item->getCount() > 0 ? $item : Item::get(Item::AIR)); $motion = $this->getMotion(); $needItem = Item::get($item->getId(), $item->getDamage()); $block = $this->getLevel()->getBlock($this->add($motion[0], $motion[1], $motion[2])); switch($block->getId()){ case Block::CHEST: case Block::TRAPPED_CHEST: case Block::DROPPER: case Block::DISPENSER: case Block::BREWING_STAND_BLOCK: case Block::FURNACE: $t = $this->getLevel()->getTile($block); /** @var Chest|Dispenser|Dropper|BrewingStand|Furnace $t */ if($t instanceof Tile){ if($t->getInventory()->canAddItem($needItem)){ $t->getInventory()->addItem($needItem); return; } } } $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $this->x + $motion[0] * 2 + 0.5), new DoubleTag("", $this->y + ($motion[1] > 0 ? $motion[1] : 0.5)), new DoubleTag("", $this->z + $motion[2] * 2 + 0.5) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", $motion[0]), new DoubleTag("", $motion[1]), new DoubleTag("", $motion[2]) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", lcg_value() * 360), new FloatTag("", 0) ]), "Health" => new ShortTag("Health", 5), "Item" => $needItem->nbtSerialize(-1, "Item"), "PickupDelay" => new ShortTag("PickupDelay", 10) ]); $f = 0.3; $itemEntity = new ItemEntity($this->getLevel(), $nbt, $this); $itemEntity->setMotion($itemEntity->getMotion()->multiply($f)); $itemEntity->spawnToAll(); for($i = 1; $i < 10; $i++){ $this->getLevel()->addParticle(new SmokeParticle($this->add($motion[0] * $i * 0.3 + 0.5, $motion[1] == 0 ? 0.5 : $motion[1] * $i * 0.3, $motion[2] * $i * 0.3 + 0.5))); } } } /** * @return CompoundTag */ public function getSpawnCompound(){ $c = new CompoundTag("", [ new StringTag("id", Tile::DROPPER), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); if($this->hasName()){ $c->CustomName = $this->namedtag->CustomName; } return $c; } }hasName() ? $this->namedtag->CustomName->getValue() : "Enchanting Table"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } /** * @return CompoundTag */ public function getSpawnCompound(){ $nbt = new CompoundTag("", [ new StringTag("id", Tile::ENCHANT_TABLE), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); if($this->hasName()){ $nbt->CustomName = $this->namedtag->CustomName; } return $nbt; } } namedtag->CustomName) ? $this->namedtag->CustomName->getValue() : "Ender Chest"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } /** * @return CompoundTag */ public function getSpawnCompound(){ $c = new CompoundTag("", [ new StringTag("id", Tile::ENDER_CHEST), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); if($this->hasName()){ $c->CustomName = $this->namedtag->CustomName; } return $c; } }item)){ $nbt->item = new ShortTag("item", 0); } if(!isset($nbt->mData)){ $nbt->mData = new IntTag("mData", 0); } parent::__construct($level, $nbt); } /** * @param Item $item * * @return bool */ public function canAddItem(Item $item) : bool{ if(!$this->isEmpty()){ return false; } switch($item->getId()){ /** @noinspection PhpMissingBreakStatementInspection */ case Item::TALL_GRASS: if($item->getDamage() === 1){ return false; } case Item::SAPLING: case Item::DEAD_BUSH: case Item::DANDELION: case Item::RED_FLOWER: case Item::BROWN_MUSHROOM: case Item::RED_MUSHROOM: case Item::CACTUS: return true; default: return false; } } /** * @return Item */ public function getItem() : Item{ return Item::get((int) ($this->namedtag["item"] ?? 0), (int) ($this->namedtag["mData"] ?? 0), 1); } /** * @param Item $item */ public function setItem(Item $item){ $this->namedtag["item"] = $item->getId(); $this->namedtag["mData"] = $item->getDamage(); $this->onChanged(); } public function removeItem(){ $this->setItem(Item::get(Item::AIR)); } /** * @return bool */ public function isEmpty() : bool{ return $this->getItem()->getId() === Item::AIR; } /** * @return CompoundTag */ public function getSpawnCompound() : CompoundTag{ return new CompoundTag("", [ new StringTag("id", Tile::FLOWER_POT), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), $this->namedtag->item, $this->namedtag->mData ]); } }BurnTime) or $nbt["BurnTime"] < 0){ $nbt->BurnTime = new ShortTag("BurnTime", 0); } if(!isset($nbt->CookTime) or !($nbt->CookTime instanceof ShortTag) or $nbt["CookTime"] < 0 or ($nbt["BurnTime"] === 0 and $nbt["CookTime"] > 0)){ $nbt->CookTime = new ShortTag("CookTime", 0); } if(!isset($nbt->MaxTime) or !($nbt->MaxTime instanceof ShortTag)){ $nbt->MaxTime = new ShortTag("BurnTime", $nbt["BurnTime"]); $nbt->BurnTicks = new ShortTag("BurnTicks", 0); } parent::__construct($level, $nbt); $this->inventory = new FurnaceInventory($this); if(!isset($this->namedtag->Items) or !($this->namedtag->Items instanceof ListTag)){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); } for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); } if($this->namedtag["BurnTime"] > 0){ $this->scheduleUpdate(); } } /** * @return string */ public function getName() : string{ return isset($this->namedtag->CustomName) ? $this->namedtag->CustomName->getValue() : "Furnace"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } public function close(){ if($this->closed === false){ foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getInventory()); } parent::close(); } } public function saveNBT(){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); for($index = 0; $index < $this->getSize(); ++$index){ $this->setItem($index, $this->inventory->getItem($index)); } } /** * @return int */ public function getSize(){ return 3; } /** * @param $index * * @return int */ protected function getSlotIndex($index){ foreach($this->namedtag->Items as $i => $slot){ if($slot["Slot"] === $index){ return $i; } } return -1; } /** * This method should not be used by plugins, use the Inventory * * @param int $index * * @return Item */ public function getItem($index){ $i = $this->getSlotIndex($index); if($i < 0){ return Item::get(Item::AIR, 0, 0); }else{ return Item::nbtDeserialize($this->namedtag->Items[$i]); } } /** * This method should not be used by plugins, use the Inventory * * @param int $index * @param Item $item * * @return bool */ public function setItem($index, Item $item){ $i = $this->getSlotIndex($index); if($item->getId() === Item::AIR or $item->getCount() <= 0){ if($i >= 0){ unset($this->namedtag->Items[$i]); } }elseif($i < 0){ for($i = 0; $i <= $this->getSize(); ++$i){ if(!isset($this->namedtag->Items[$i])){ break; } } $this->namedtag->Items[$i] = $item->nbtSerialize($index); }else{ $this->namedtag->Items[$i] = $item->nbtSerialize($index); } return true; } /** * @return FurnaceInventory */ public function getInventory(){ return $this->inventory; } /** * @param Item $fuel */ protected function checkFuel(Item $fuel){ $this->server->getPluginManager()->callEvent($ev = new FurnaceBurnEvent($this, $fuel, $fuel->getFuelTime())); if($ev->isCancelled()){ return; } $this->namedtag->MaxTime = new ShortTag("MaxTime", $ev->getBurnTime()); $this->namedtag->BurnTime = new ShortTag("BurnTime", $ev->getBurnTime()); $this->namedtag->BurnTicks = new ShortTag("BurnTicks", 0); if($this->getBlock()->getId() === Item::FURNACE){ $this->getLevel()->setBlock($this, Block::get(Item::BURNING_FURNACE, $this->getBlock()->getDamage()), true); } if($this->namedtag["BurnTime"] > 0 and $ev->isBurning()){ if($fuel->getId() === Item::BUCKET and $fuel->getDamage() === Item::LAVA){ $fuel = Item::get(Item::BUCKET, 0, 1); $this->inventory->setFuel($fuel); }else{ $fuel->setCount($fuel->getCount() - 1); if($fuel->getCount() === 0){ $fuel = Item::get(Item::AIR, 0, 0); } $this->inventory->setFuel($fuel); } } } /** * @return bool */ public function onUpdate(){ if($this->closed === true){ return false; } $this->timings->startTiming(); $ret = false; $fuel = $this->inventory->getFuel(); $raw = $this->inventory->getSmelting(); $product = $this->inventory->getResult(); $smelt = $this->server->getCraftingManager()->matchFurnaceRecipe($raw); $canSmelt = ($smelt instanceof FurnaceRecipe and $raw->getCount() > 0 and (($smelt->getResult()->equals($product) and $product->getCount() < $product->getMaxStackSize()) or $product->getId() === Item::AIR)); if($this->namedtag["BurnTime"] <= 0 and $canSmelt and $fuel->getFuelTime() !== null and $fuel->getCount() > 0){ $this->checkFuel($fuel); } if($this->namedtag["BurnTime"] > 0){ $this->namedtag->BurnTime = new ShortTag("BurnTime", $this->namedtag["BurnTime"] - 1); $this->namedtag->BurnTicks = new ShortTag("BurnTicks", ceil(($this->namedtag["BurnTime"] / $this->namedtag["MaxTime"] * 200))); if($smelt instanceof FurnaceRecipe and $canSmelt){ $this->namedtag->CookTime = new ShortTag("CookTime", $this->namedtag["CookTime"] + 1); if($this->namedtag["CookTime"] >= 200){ //10 seconds $product = Item::get($smelt->getResult()->getId(), $smelt->getResult()->getDamage(), $product->getCount() + 1); $this->server->getPluginManager()->callEvent($ev = new FurnaceSmeltEvent($this, $raw, $product)); if(!$ev->isCancelled()){ $this->inventory->setResult($ev->getResult()); $raw->setCount($raw->getCount() - 1); if($raw->getCount() === 0){ $raw = Item::get(Item::AIR, 0, 0); } $this->inventory->setSmelting($raw); } $this->namedtag->CookTime = new ShortTag("CookTime", $this->namedtag["CookTime"] - 200); } }elseif($this->namedtag["BurnTime"] <= 0){ $this->namedtag->BurnTime = new ShortTag("BurnTime", 0); $this->namedtag->CookTime = new ShortTag("CookTime", 0); $this->namedtag->BurnTicks = new ShortTag("BurnTicks", 0); }else{ $this->namedtag->CookTime = new ShortTag("CookTime", 0); } $ret = true; }else{ if($this->getBlock()->getId() === Item::BURNING_FURNACE){ $this->getLevel()->setBlock($this, Block::get(Item::FURNACE, $this->getBlock()->getDamage()), true); } $this->namedtag->BurnTime = new ShortTag("BurnTime", 0); $this->namedtag->CookTime = new ShortTag("CookTime", 0); $this->namedtag->BurnTicks = new ShortTag("BurnTicks", 0); } foreach($this->getInventory()->getViewers() as $player){ $windowId = $player->getWindowId($this->getInventory()); if($windowId > 0){ $pk = new ContainerSetDataPacket(); $pk->windowid = $windowId; $pk->property = 0; //Smelting $pk->value = floor($this->namedtag["CookTime"]); $player->dataPacket($pk); $pk = new ContainerSetDataPacket(); $pk->windowid = $windowId; $pk->property = 1; //Fire icon $pk->value = $this->namedtag["BurnTicks"]; $player->dataPacket($pk); } } $this->lastUpdate = microtime(true); $this->timings->stopTiming(); return $ret; } /** * @return CompoundTag */ public function getSpawnCompound(){ $nbt = new CompoundTag("", [ new StringTag("id", Tile::FURNACE), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), new ShortTag("BurnTime", $this->namedtag["BurnTime"]), new ShortTag("CookTime", $this->namedtag["CookTime"]), //new ShortTag("BurnDuration", $this->namedtag["BurnTicks"]) ]); if($this->hasName()){ $nbt->CustomName = $this->namedtag->CustomName; } return $nbt; } } TransferCooldown) or !($nbt->TransferCooldown instanceof IntTag)){ $nbt->TransferCooldown = new IntTag("TransferCooldown", 0); } parent::__construct($level, $nbt); $this->inventory = new HopperInventory($this); if(!isset($this->namedtag->Items) or !($this->namedtag->Items instanceof ListTag)){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); } for($i = 0; $i < $this->getSize(); ++$i){ $this->inventory->setItem($i, $this->getItem($i)); } $this->scheduleUpdate(); } public function close(){ if($this->closed === false){ foreach($this->getInventory()->getViewers() as $player){ $player->removeWindow($this->getInventory()); } parent::close(); } } public function activate(){ $this->isPowered = true; } public function deactivate(){ $this->isPowered = false; } /** * @return bool */ public function canUpdate(){ return $this->namedtag->TransferCooldown->getValue() === 0 and !$this->isPowered; } public function resetCooldownTicks(){ $this->namedtag->TransferCooldown->setValue(8); } /** * @return bool */ public function onUpdate(){ if(!($this->getBlock() instanceof HopperBlock)){ return false; } //Pickup dropped items //This can happen at any time regardless of cooldown $area = clone $this->getBlock()->getBoundingBox(); //Area above hopper to draw items from $area->maxY = ceil($area->maxY) + 1; //Account for full block above, not just 1 + 5/8 foreach($this->getLevel()->getChunkEntities($this->getBlock()->x >> 4, $this->getBlock()->z >> 4) as $entity){ if(!($entity instanceof DroppedItem) or !$entity->isAlive()){ continue; } if(!$entity->boundingBox->intersectsWith($area)){ continue; } $item = $entity->getItem(); if(!$item instanceof Item){ continue; } if($item->getCount() < 1){ $entity->kill(); continue; } if($this->inventory->canAddItem($item)){ $this->inventory->addItem($item); $entity->kill(); } } if(!$this->canUpdate()){ //Hoppers only update CONTENTS every 8th tick $this->namedtag->TransferCooldown->setValue($this->namedtag->TransferCooldown->getValue() - 1); return true; } //Suck items from above tile inventories $source = $this->getLevel()->getTile($this->getBlock()->getSide(Vector3::SIDE_UP)); if($source instanceof Tile and $source instanceof InventoryHolder){ $inventory = $source->getInventory(); $item = clone $inventory->getItem($inventory->firstOccupied()); $item->setCount(1); if($this->inventory->canAddItem($item)){ $this->inventory->addItem($item); $inventory->removeItem($item); $this->resetCooldownTicks(); if($source instanceof Hopper){ $source->resetCooldownTicks(); } } } //Feed item into target inventory //Do not do this if there's a hopper underneath this hopper, to follow vanilla behaviour if(!($this->getLevel()->getTile($this->getBlock()->getSide(Vector3::SIDE_DOWN)) instanceof Hopper)){ $target = $this->getLevel()->getTile($this->getBlock()->getSide($this->getBlock()->getDamage())); if($target instanceof Tile and $target instanceof InventoryHolder){ $inv = $target->getInventory(); foreach($this->inventory->getContents() as $item){ if($item->getId() === Item::AIR or $item->getCount() < 1){ continue; } $targetItem = clone $item; $targetItem->setCount(1); if($inv->canAddItem($targetItem)){ $inv->addItem($targetItem); $this->inventory->removeItem($targetItem); $this->resetCooldownTicks(); if($target instanceof Hopper){ $target->resetCooldownTicks(); } break; } } } } return true; } /** * @return HopperInventory */ public function getInventory(){ return $this->inventory; } /** * @return int */ public function getSize(){ return 5; } /** * This method should not be used by plugins, use the Inventory * * @param int $index * * @return Item */ public function getItem($index){ $i = $this->getSlotIndex($index); if($i < 0){ return Item::get(Item::AIR, 0, 0); }else{ return Item::nbtDeserialize($this->namedtag->Items[$i]); } } /** * This method should not be used by plugins, use the Inventory * * @param int $index * @param Item $item * * @return bool */ public function setItem($index, Item $item){ $i = $this->getSlotIndex($index); if($item->getId() === Item::AIR or $item->getCount() <= 0){ if($i >= 0){ unset($this->namedtag->Items[$i]); } }elseif($i < 0){ for($i = 0; $i <= $this->getSize(); ++$i){ if(!isset($this->namedtag->Items[$i])){ break; } } $this->namedtag->Items[$i] = $item->nbtSerialize($index); }else{ $this->namedtag->Items[$i] = $item->nbtSerialize($index); } return true; } /** * @param $index * * @return int */ protected function getSlotIndex($index){ foreach($this->namedtag->Items as $i => $slot){ if((int) $slot["Slot"] === (int) $index){ return (int) $i; } } return -1; } public function saveNBT(){ $this->namedtag->Items = new ListTag("Items", []); $this->namedtag->Items->setTagType(NBT::TAG_Compound); for($index = 0; $index < $this->getSize(); ++$index){ $this->setItem($index, $this->inventory->getItem($index)); } } /** * @return string */ public function getName() : string{ return isset($this->namedtag->CustomName) ? $this->namedtag->CustomName->getValue() : "Hopper"; } /** * @return bool */ public function hasName(){ return isset($this->namedtag->CustomName); } /** * @param void $str */ public function setName($str){ if($str === ""){ unset($this->namedtag->CustomName); return; } $this->namedtag->CustomName = new StringTag("CustomName", $str); } /** * @return bool */ public function hasLock(){ return isset($this->namedtag->Lock); } /** * @param string $itemName */ public function setLock(string $itemName = ""){ if($itemName === ""){ unset($this->namedtag->Lock); return; } $this->namedtag->Lock = new StringTag("Lock", $itemName); } /** * @param string $key * * @return bool */ public function checkLock(string $key){ return $this->namedtag->Lock->getValue() === $key; } /** * @return CompoundTag */ public function getSpawnCompound(){ $c = new CompoundTag("", [ new StringTag("id", Tile::HOPPER), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); if($this->hasName()){ $c->CustomName = $this->namedtag->CustomName; } if($this->hasLock()){ $c->Lock = $this->namedtag->Lock; } return $c; } } ItemRotation)){ $nbt->ItemRotation = new ByteTag("ItemRotation", 0); } if(!isset($nbt->ItemDropChance)){ $nbt->ItemDropChance = new FloatTag("ItemDropChance", 1.0); } parent::__construct($level, $nbt); } /** * @return bool */ public function hasItem() : bool{ return $this->getItem()->getId() !== Item::AIR; } /** * @return Item */ public function getItem() : Item{ if(isset($this->namedtag->Item)){ return Item::nbtDeserialize($this->namedtag->Item); }else{ return Item::get(Item::AIR); } } /** * @param Item|null $item */ public function setItem(Item $item = null){ if($item !== null and $item->getId() !== Item::AIR){ $this->namedtag->Item = $item->nbtSerialize(-1, "Item"); }else{ unset($this->namedtag->Item); } $this->onChanged(); } /** * @return int */ public function getItemRotation() : int{ return $this->namedtag->ItemRotation->getValue(); } /** * @param int $rotation */ public function setItemRotation(int $rotation){ $this->namedtag->ItemRotation = new ByteTag("ItemRotation", $rotation); $this->onChanged(); } /** * @return float */ public function getItemDropChance() : float{ return $this->namedtag->ItemDropChance->getValue(); } /** * @param float $chance */ public function setItemDropChance(float $chance){ $this->namedtag->ItemDropChance = new FloatTag("ItemDropChance", $chance); $this->onChanged(); } /** * @param string $mapid */ public function SetMapID(string $mapid){ $this->map_uuid = $mapid; $this->namedtag->Map_UUID = new StringTag("map_uuid", $mapid); $this->onChanged(); } /** * @return string */ public function getMapID() : string{ return $this->map_uuid; } /** * @return CompoundTag */ public function getSpawnCompound(){ $tag = new CompoundTag("", [ new StringTag("id", Tile::ITEM_FRAME), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), $this->namedtag->ItemDropChance, $this->namedtag->ItemRotation, ]); if($this->hasItem()){ $tag->Item = $this->namedtag->Item; if($this->getItem()->getId() === Item::FILLED_MAP){ if(isset($this->namedtag->Map_UUID)){ $tag->Map_UUID = $this->namedtag->Map_UUID; } } } return $tag; } }EntityId) or !($nbt->EntityId instanceof IntTag)){ $nbt->EntityId = new IntTag("EntityId", 0); } if(!isset($nbt->SpawnCount) or !($nbt->SpawnCount instanceof IntTag)){ $nbt->SpawnCount = new IntTag("SpawnCount", 4); } if(!isset($nbt->SpawnRange) or !($nbt->SpawnRange instanceof IntTag)){ $nbt->SpawnRange = new IntTag("SpawnRange", 4); } if(!isset($nbt->MinSpawnDelay) or !($nbt->MinSpawnDelay instanceof IntTag)){ $nbt->MinSpawnDelay = new IntTag("MinSpawnDelay", 200); } if(!isset($nbt->MaxSpawnDelay) or !($nbt->MaxSpawnDelay instanceof IntTag)){ $nbt->MaxSpawnDelay = new IntTag("MaxSpawnDelay", 799); } if(!isset($nbt->Delay) or !($nbt->Delay instanceof IntTag)){ $nbt->Delay = new IntTag("Delay", mt_rand($nbt->MinSpawnDelay->getValue(), $nbt->MaxSpawnDelay->getValue())); } parent::__construct($level, $nbt); if($this->getEntityId() > 0){ $this->scheduleUpdate(); } } /** * @return null */ public function getEntityId(){ return $this->namedtag["EntityId"]; } /** * @param int $id */ public function setEntityId(int $id){ $this->namedtag->EntityId->setValue($id); $this->onChanged(); $this->scheduleUpdate(); } /** * @return null */ public function getSpawnCount(){ return $this->namedtag["SpawnCount"]; } /** * @param int $value */ public function setSpawnCount(int $value){ $this->namedtag->SpawnCount->setValue($value); } /** * @return null */ public function getSpawnRange(){ return $this->namedtag["SpawnRange"]; } /** * @param int $value */ public function setSpawnRange(int $value){ $this->namedtag->SpawnRange->setValue($value); } /** * @return null */ public function getMinSpawnDelay(){ return $this->namedtag["MinSpawnDelay"]; } /** * @param int $value */ public function setMinSpawnDelay(int $value){ $this->namedtag->MinSpawnDelay->setValue($value); } /** * @return null */ public function getMaxSpawnDelay(){ return $this->namedtag["MaxSpawnDelay"]; } /** * @param int $value */ public function setMaxSpawnDelay(int $value){ $this->namedtag->MaxSpawnDelay->setValue($value); } /** * @return null */ public function getDelay(){ return $this->namedtag["Delay"]; } /** * @param int $value */ public function setDelay(int $value){ $this->namedtag->Delay->setValue($value); } /** * @return string */ public function getName() : string{ return "Monster Spawner"; } /** * @return bool */ public function canUpdate() : bool{ if($this->getEntityId() === 0) return false; $hasPlayer = false; $count = 0; foreach($this->getLevel()->getEntities() as $e){ if($e instanceof Player){ if($e->distance($this->getBlock()) <= 15) $hasPlayer = true; } if($e::NETWORK_ID == $this->getEntityId()){ $count++; } } if($hasPlayer and $count < 15){ // Spawn limit = 15 return true; } return false; } /** * @return bool */ public function onUpdate(){ if($this->closed === true){ return false; } $this->timings->startTiming(); if(!($this->chunk instanceof Chunk)){ return false; } if($this->canUpdate()){ if($this->getDelay() <= 0){ $success = 0; for($i = 0; $i < $this->getSpawnCount(); $i++){ $pos = $this->add(mt_rand() / mt_getrandmax() * $this->getSpawnRange(), mt_rand(-1, 1), mt_rand() / mt_getrandmax() * $this->getSpawnRange()); $target = $this->getLevel()->getBlock($pos); $ground = $target->getSide(Vector3::SIDE_DOWN); if($target->getId() == Item::AIR && $ground->isTopFacingSurfaceSolid()){ $success++; $this->getLevel()->getServer()->getPluginManager()->callEvent($ev = new EntityGenerateEvent($pos, $this->getEntityId(), EntityGenerateEvent::CAUSE_MOB_SPAWNER)); if(!$ev->isCancelled()){ $nbt = new CompoundTag("", [ "Pos" => new ListTag("Pos", [ new DoubleTag("", $pos->x), new DoubleTag("", $pos->y), new DoubleTag("", $pos->z) ]), "Motion" => new ListTag("Motion", [ new DoubleTag("", 0), new DoubleTag("", 0), new DoubleTag("", 0) ]), "Rotation" => new ListTag("Rotation", [ new FloatTag("", mt_rand() / mt_getrandmax() * 360), new FloatTag("", 0) ]), ]); $entity = Entity::createEntity($this->getEntityId(), $this->getLevel(), $nbt); $entity->spawnToAll(); } } } if($success > 0){ $this->setDelay(mt_rand($this->getMinSpawnDelay(), $this->getMaxSpawnDelay())); } }else{ $this->setDelay($this->getDelay() - 1); } } $this->timings->stopTiming(); return true; } /** * @return CompoundTag */ public function getSpawnCompound(){ $c = new CompoundTag("", [ new StringTag("id", Tile::MOB_SPAWNER), new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), new IntTag("EntityId", (int) $this->getEntityId()) ]); return $c; } } Text1)){ $nbt->Text1 = new StringTag("Text1", ""); } if(!isset($nbt->Text2) or !($nbt->Text2 instanceof StringTag)){ $nbt->Text2 = new StringTag("Text2", ""); } if(!isset($nbt->Text3) or !($nbt->Text3 instanceof StringTag)){ $nbt->Text3 = new StringTag("Text3", ""); } if(!isset($nbt->Text4) or !($nbt->Text4 instanceof StringTag)){ $nbt->Text4 = new StringTag("Text4", ""); } parent::__construct($level, $nbt); } public function saveNBT(){ parent::saveNBT(); unset($this->namedtag->Creator); } /** * @param string $line1 * @param string $line2 * @param string $line3 * @param string $line4 * * @return bool */ public function setText($line1 = "", $line2 = "", $line3 = "", $line4 = ""){ $this->namedtag->Text1 = new StringTag("Text1", $line1); $this->namedtag->Text2 = new StringTag("Text2", $line2); $this->namedtag->Text3 = new StringTag("Text3", $line3); $this->namedtag->Text4 = new StringTag("Text4", $line4); $this->onChanged(); return true; } /** * @return array */ public function getText(){ return [ $this->namedtag["Text1"], $this->namedtag["Text2"], $this->namedtag["Text3"], $this->namedtag["Text4"] ]; } /** * @return CompoundTag */ public function getSpawnCompound(){ return new CompoundTag("", [ new StringTag("id", Tile::SIGN), $this->namedtag->Text1, $this->namedtag->Text2, $this->namedtag->Text3, $this->namedtag->Text4, new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z) ]); } /** * @param CompoundTag $nbt * @param Player $player * * @return bool */ public function updateCompoundTag(CompoundTag $nbt, Player $player) : bool{ if($nbt["id"] !== Tile::SIGN){ return false; } $ev = new SignChangeEvent($this->getBlock(), $player, [ TextFormat::clean($nbt["Text1"], ($removeFormat = $player->getRemoveFormat())), TextFormat::clean($nbt["Text2"], $removeFormat), TextFormat::clean($nbt["Text3"], $removeFormat), TextFormat::clean($nbt["Text4"], $removeFormat) ]); if(!isset($this->namedtag->Creator) or $this->namedtag["Creator"] !== $player->getRawUniqueId()){ $ev->setCancelled(); } $this->level->getServer()->getPluginManager()->callEvent($ev); if(!$ev->isCancelled()){ $this->setText(...$ev->getLines()); return true; }else{ return false; } } } SkullType)){ $nbt->SkullType = new ByteTag("SkullType", 0); } if(!isset($nbt->Rot) or !($nbt->Rot instanceof ByteTag)){ $nbt->Rot = new ByteTag("Rot", 0); } parent::__construct($level, $nbt); } /** * @param int $type * * @return bool */ public function setType(int $type){ if($type >= 0 && $type <= 4){ $this->namedtag->SkullType = new ByteTag("SkullType", $type); $this->onChanged(); return true; } return false; } /** * @return null */ public function getType(){ return $this->namedtag["SkullType"]; } public function saveNBT(){ parent::saveNBT(); unset($this->namedtag->Creator); } /** * @return CompoundTag */ public function getSpawnCompound(){ return new CompoundTag("", [ new StringTag("id", Tile::SKULL), $this->namedtag->SkullType, $this->namedtag->Rot, new IntTag("x", (int) $this->x), new IntTag("y", (int) $this->y), new IntTag("z", (int) $this->z), ]); } } closed){ return false; } $nbt = new NBT(NBT::LITTLE_ENDIAN); $nbt->setData($this->getSpawnCompound()); $pk = new BlockEntityDataPacket(); $pk->x = $this->x; $pk->y = $this->y; $pk->z = $this->z; $pk->namedtag = $nbt->write(true); $player->dataPacket($pk); return true; } /** * Spawnable constructor. * * @param Level $level * @param CompoundTag $nbt */ public function __construct(Level $level, CompoundTag $nbt){ parent::__construct($level, $nbt); $this->spawnToAll(); } public function spawnToAll(){ if($this->closed){ return; } foreach($this->getLevel()->getChunkPlayers($this->chunk->getX(), $this->chunk->getZ()) as $player){ if($player->spawned === true){ $this->spawnTo($player); } } } protected function onChanged(){ $this->spawnToAll(); if($this->chunk !== null){ $this->chunk->setChanged(); $this->level->clearChunkCache($this->chunk->getX(), $this->chunk->getZ()); } } /** * @return CompoundTag */ public abstract function getSpawnCompound(); /** * Called when a player updates a block entity's NBT data * for example when writing on a sign. * * @param CompoundTag $nbt * @param Player $player * * @return bool indication of success, will respawn the tile to the player if false. */ public function updateCompoundTag(CompoundTag $nbt, Player $player) : bool{ return false; } } isAbstract()){ self::$knownTiles[$class->getShortName()] = $className; self::$shortNames[$className] = $class->getShortName(); return true; } return false; } /** * Returns the short save name * * @return string */ public function getSaveId(){ return self::$shortNames[static::class]; } /** * Tile constructor. * * @param Level $level * @param CompoundTag $nbt */ public function __construct(Level $level, CompoundTag $nbt){ $this->timings = Timings::getTileEntityTimings($this); $this->namedtag = $nbt; $this->server = $level->getServer(); $this->setLevel($level); $this->chunk = $level->getChunk($this->namedtag["x"] >> 4, $this->namedtag["z"] >> 4, false); assert($this->chunk !== null); $this->name = ""; $this->lastUpdate = microtime(true); $this->id = Tile::$tileCount++; $this->x = (int) $this->namedtag["x"]; $this->y = (int) $this->namedtag["y"]; $this->z = (int) $this->namedtag["z"]; $this->chunk->addTile($this); $this->getLevel()->addTile($this); $this->tickTimer = Timings::getTileEntityTimings($this); } /** * @return int */ public function getId(){ return $this->id; } public function saveNBT(){ $this->namedtag->id = new StringTag("id", $this->getSaveId()); $this->namedtag->x = new IntTag("x", $this->x); $this->namedtag->y = new IntTag("y", $this->y); $this->namedtag->z = new IntTag("z", $this->z); } /** * @return \pocketmine\block\Block */ public function getBlock(){ return $this->level->getBlock($this); } /** * @return bool */ public function onUpdate(){ return false; } public final function scheduleUpdate(){ $this->level->updateTiles[$this->id] = $this; } public function __destruct(){ $this->close(); } public function close(){ if(!$this->closed){ $this->closed = true; unset($this->level->updateTiles[$this->id]); if($this->chunk instanceof Chunk){ $this->chunk->removeTile($this); } if(($level = $this->getLevel()) instanceof Level){ $level->removeTile($this); } $this->level = null; } } /** * @return string */ public function getName() : string{ return $this->name; } } > 56; }else{ return $b << 24 >> 24; } }else{ return $b; } } /** * Writes an unsigned/signed byte * * @param $c * * @return string */ public static function writeByte($c){ return chr($c); } /** * Reads a 16-bit unsigned big-endian number * * @param $str * * @return int */ public static function readShort($str){ self::checkLength($str, 2); return unpack("n", $str)[1]; } /** * Reads a 16-bit signed big-endian number * * @param $str * * @return int */ public static function readSignedShort($str){ self::checkLength($str, 2); if(PHP_INT_SIZE === 8){ return unpack("n", $str)[1] << 48 >> 48; }else{ return unpack("n", $str)[1] << 16 >> 16; } } /** * Writes a 16-bit signed/unsigned big-endian number * * @param $value * * @return string */ public static function writeShort($value){ return pack("n", $value); } /** * Reads a 16-bit unsigned little-endian number * * @param $str * * @return int */ public static function readLShort($str){ self::checkLength($str, 2); return unpack("v", $str)[1]; } /** * Reads a 16-bit signed little-endian number * * @param $str * * @return int */ public static function readSignedLShort($str){ self::checkLength($str, 2); if(PHP_INT_SIZE === 8){ return unpack("v", $str)[1] << 48 >> 48; }else{ return unpack("v", $str)[1] << 16 >> 16; } } /** * Writes a 16-bit signed/unsigned little-endian number * * @param $value * * @return string */ public static function writeLShort($value){ return pack("v", $value); } /** * @param $str * * @return int */ public static function readInt($str){ self::checkLength($str, 4); if(PHP_INT_SIZE === 8){ return unpack("N", $str)[1] << 32 >> 32; }else{ return unpack("N", $str)[1]; } } /** * @param $value * * @return string */ public static function writeInt($value){ return pack("N", $value); } /** * @param $str * * @return int */ public static function readLInt($str){ self::checkLength($str, 4); if(PHP_INT_SIZE === 8){ return unpack("V", $str)[1] << 32 >> 32; }else{ return unpack("V", $str)[1]; } } /** * @param $value * * @return string */ public static function writeLInt($value){ return pack("V", $value); } /** * @param $str * @param int $accuracy * * @return float */ public static function readFloat($str, int $accuracy = -1){ self::checkLength($str, 4); $value = ENDIANNESS === self::BIG_ENDIAN ? unpack("f", $str)[1] : unpack("f", strrev($str))[1]; if($accuracy > -1){ return round($value, $accuracy); }else{ return $value; } } /** * @param $value * * @return string */ public static function writeFloat($value){ return ENDIANNESS === self::BIG_ENDIAN ? pack("f", $value) : strrev(pack("f", $value)); } /** * @param $str * @param int $accuracy * * @return float */ public static function readLFloat($str, int $accuracy = -1){ self::checkLength($str, 4); $value = ENDIANNESS === self::BIG_ENDIAN ? unpack("f", strrev($str))[1] : unpack("f", $str)[1]; if($accuracy > -1){ return round($value, $accuracy); }else{ return $value; } } /** * @param $value * * @return string */ public static function writeLFloat($value){ return ENDIANNESS === self::BIG_ENDIAN ? strrev(pack("f", $value)) : pack("f", $value); } /** * @param $value * * @return mixed */ public static function printFloat($value){ return preg_replace("/(\\.\\d+?)0+$/", "$1", sprintf("%F", $value)); } /** * @param $str * * @return mixed */ public static function readDouble($str){ self::checkLength($str, 8); return ENDIANNESS === self::BIG_ENDIAN ? unpack("d", $str)[1] : unpack("d", strrev($str))[1]; } /** * @param $value * * @return string */ public static function writeDouble($value){ return ENDIANNESS === self::BIG_ENDIAN ? pack("d", $value) : strrev(pack("d", $value)); } /** * @param $str * * @return mixed */ public static function readLDouble($str){ self::checkLength($str, 8); return ENDIANNESS === self::BIG_ENDIAN ? unpack("d", strrev($str))[1] : unpack("d", $str)[1]; } /** * @param $value * * @return string */ public static function writeLDouble($value){ return ENDIANNESS === self::BIG_ENDIAN ? strrev(pack("d", $value)) : pack("d", $value); } /** * @param $x * * @return int|string */ public static function readLong($x){ self::checkLength($x, 8); if(PHP_INT_SIZE === 8){ $int = unpack("N*", $x); return ($int[1] << 32) | $int[2]; }else{ $value = "0"; for($i = 0; $i < 8; $i += 2){ $value = bcmul($value, "65536", 0); $value = bcadd($value, self::readShort(substr($x, $i, 2)), 0); } if(bccomp($value, "9223372036854775807") == 1){ $value = bcadd($value, "-18446744073709551616"); } return $value; } } /** * @param $value * * @return string */ public static function writeLong($value){ if(PHP_INT_SIZE === 8){ return pack("NN", $value >> 32, $value & 0xFFFFFFFF); }else{ $x = ""; if(bccomp($value, "0") == -1){ $value = bcadd($value, "18446744073709551616"); } $x .= self::writeShort(bcmod(bcdiv($value, "281474976710656"), "65536")); $x .= self::writeShort(bcmod(bcdiv($value, "4294967296"), "65536")); $x .= self::writeShort(bcmod(bcdiv($value, "65536"), "65536")); $x .= self::writeShort(bcmod($value, "65536")); return $x; } } /** * @param $str * * @return int|string */ public static function readLLong($str){ return self::readLong(strrev($str)); } /** * @param $value * * @return string */ public static function writeLLong($value){ return strrev(self::writeLong($value)); } //TODO: proper varlong support /** * @param $stream * * @return int */ public static function readVarInt($stream){ $shift = PHP_INT_SIZE === 8 ? 63 : 31; $raw = self::readUnsignedVarInt($stream); $temp = ((($raw << $shift) >> $shift) ^ $raw) >> 1; return $temp ^ ($raw & (1 << $shift)); } /** * @param $stream * * @return int */ public static function readUnsignedVarInt($stream){ $value = 0; $i = 0; do{ if($i > 63){ throw new \InvalidArgumentException("Varint did not terminate after 10 bytes!"); } $value |= ((($b = $stream->getByte()) & 0x7f) << $i); $i += 7; }while($b & 0x80); return $value; } /** * @param $v * * @return string */ public static function writeVarInt($v){ return self::writeUnsignedVarInt(($v << 1) ^ ($v >> (PHP_INT_SIZE === 8 ? 63 : 31))); } /** * @param $value * * @return string */ public static function writeUnsignedVarInt($value){ $buf = ""; for($i = 0; $i < 10; ++$i){ if(($value >> 7) !== 0){ $buf .= chr($value | 0x80); //Let chr() take the last byte of this, it's faster than adding another & 0x7f. }else{ $buf .= chr($value & 0x7f); return $buf; } $value = (($value >> 7) & (PHP_INT_MAX >> 6)); //PHP really needs a logical right-shift operator } throw new \InvalidArgumentException("Value too large to be encoded as a varint"); } } #ifndef COMPILE #endif use pocketmine\item\Item; class BinaryStream extends \stdClass { public $offset; public $buffer; /** * BinaryStream constructor. * * @param string $buffer * @param int $offset */ public function __construct($buffer = "", $offset = 0){ $this->buffer = $buffer; $this->offset = $offset; } public function reset(){ $this->buffer = ""; $this->offset = 0; } /** * @param null $buffer * @param int $offset */ public function setBuffer($buffer = null, $offset = 0){ $this->buffer = $buffer; $this->offset = (int) $offset; } /** * @return int */ public function getOffset(){ return $this->offset; } /** * @return string */ public function getBuffer(){ return $this->buffer; } /** * @param $len * * @return bool|string */ public function get($len){ if($len < 0){ $this->offset = strlen($this->buffer) - 1; return ""; }elseif($len === true){ $str = substr($this->buffer, $this->offset); $this->offset = strlen($this->buffer); return $str; } return $len === 1 ? $this->buffer{$this->offset++} : substr($this->buffer, ($this->offset += $len) - $len, $len); } /** * @param $str */ public function put($str){ $this->buffer .= $str; } /** * @return bool */ public function getBool() : bool{ return (bool) $this->getByte(); } /** * @param $v */ public function putBool($v){ $this->putByte((bool) $v); } /** * @return int|string */ public function getLong(){ return Binary::readLong($this->get(8)); } /** * @param $v */ public function putLong($v){ $this->buffer .= Binary::writeLong($v); } /** * @return int */ public function getInt(){ return Binary::readInt($this->get(4)); } /** * @param $v */ public function putInt($v){ $this->buffer .= Binary::writeInt($v); } /** * @return int|string */ public function getLLong(){ return Binary::readLLong($this->get(8)); } /** * @param $v */ public function putLLong($v){ $this->buffer .= Binary::writeLLong($v); } /** * @return int */ public function getLInt(){ return Binary::readLInt($this->get(4)); } /** * @param $v */ public function putLInt($v){ $this->buffer .= Binary::writeLInt($v); } /** * @return int */ public function getSignedShort(){ return Binary::readSignedShort($this->get(2)); } /** * @param $v */ public function putShort($v){ $this->buffer .= Binary::writeShort($v); } /** * @return int */ public function getShort(){ return Binary::readShort($this->get(2)); } /** * @param $v */ public function putSignedShort($v){ $this->buffer .= Binary::writeShort($v); } /** * @param int $accuracy * * @return float */ public function getFloat(int $accuracy = -1){ return Binary::readFloat($this->get(4), $accuracy); } /** * @param $v */ public function putFloat($v){ $this->buffer .= Binary::writeFloat($v); } /** * @param bool $signed * * @return int */ public function getLShort($signed = true){ return $signed ? Binary::readSignedLShort($this->get(2)) : Binary::readLShort($this->get(2)); } /** * @param $v */ public function putLShort($v){ $this->buffer .= Binary::writeLShort($v); } /** * @param int $accuracy * * @return float */ public function getLFloat(int $accuracy = -1){ return Binary::readLFloat($this->get(4), $accuracy); } /** * @param $v */ public function putLFloat($v){ $this->buffer .= Binary::writeLFloat($v); } /** * @return mixed */ public function getTriad(){ return Binary::readTriad($this->get(3)); } /** * @param $v */ public function putTriad($v){ $this->buffer .= Binary::writeTriad($v); } /** * @return mixed */ public function getLTriad(){ return Binary::readLTriad($this->get(3)); } /** * @param $v */ public function putLTriad($v){ $this->buffer .= Binary::writeLTriad($v); } /** * @return int */ public function getByte(){ return ord($this->buffer{$this->offset++}); } /** * @param $v */ public function putByte($v){ $this->buffer .= chr($v); } /** * @return UUID */ public function getUUID(){ return UUID::fromBinary($this->get(16)); } /** * @param UUID $uuid */ public function putUUID(UUID $uuid){ $this->put($uuid->toBinary()); } /** * @return Item */ public function getSlot(){ $id = $this->getVarInt(); if($id <= 0){ return Item::get(0, 0, 0); } $auxValue = $this->getVarInt(); $data = $auxValue >> 8; if($data === 0x7fff){ $data = -1; } $cnt = $auxValue & 0xff; $nbtLen = $this->getLShort(); $nbt = ""; if($nbtLen > 0){ $nbt = $this->get($nbtLen); } $canPlaceOn = $this->getVarInt(); if($canPlaceOn > 0){ for($i = 0; $i < $canPlaceOn; ++$i){ $this->getString(); } } $canDestroy = $this->getVarInt(); if($canDestroy > 0){ for($i = 0; $i < $canDestroy; ++$i){ $this->getString(); } } return Item::get($id, $data, $cnt, $nbt); } /** * @param Item $item */ public function putSlot(Item $item){ if($item->getId() === 0){ $this->putVarInt(0); return; } $this->putVarInt($item->getId()); $auxValue = (($item->getDamage() & 0x7fff) << 8) | $item->getCount(); $this->putVarInt($auxValue); $nbt = $item->getCompoundTag(); $this->putLShort(strlen($nbt)); $this->put($nbt); $this->putVarInt(0); //CanPlaceOn entry count (TODO) $this->putVarInt(0); //CanDestroy entry count (TODO) } /** * @return bool|string */ public function getString(){ return $this->get($this->getUnsignedVarInt()); } /** * @param $v */ public function putString($v){ $this->putUnsignedVarInt(strlen($v)); $this->put($v); } //TODO: varint64 /** * Reads an unsigned varint32 from the stream. */ public function getUnsignedVarInt(){ return Binary::readUnsignedVarInt($this); } /** * Writes an unsigned varint32 to the stream. * * @param $v */ public function putUnsignedVarInt($v){ $this->put(Binary::writeUnsignedVarInt($v)); } /** * Reads a signed varint32 from the stream. */ public function getVarInt(){ return Binary::readVarInt($this); } /** * Writes a signed varint32 to the stream. * * @param $v */ public function putVarInt($v){ $this->put(Binary::writeVarInt($v)); } /** * @return int */ public function getEntityId(){ return $this->getVarInt(); } /** * @param $v */ public function putEntityId($v){ $this->putVarInt($v); } /** * @param $x * @param $y * @param $z */ public function getBlockCoords(&$x, &$y, &$z){ $x = $this->getVarInt(); $y = $this->getUnsignedVarInt(); $z = $this->getVarInt(); } /** * @param $x * @param $y * @param $z */ public function putBlockCoords($x, $y, $z){ $this->putVarInt($x); $this->putUnsignedVarInt($y); $this->putVarInt($z); } /** * @param $x * @param $y * @param $z */ public function getVector3f(&$x, &$y, &$z){ $x = $this->getLFloat(4); $y = $this->getLFloat(4); $z = $this->getLFloat(4); } /** * @param $x * @param $y * @param $z */ public function putVector3f($x, $y, $z){ $this->putLFloat($x); $this->putLFloat($y); $this->putLFloat($z); } /** * @return bool */ public function feof(){ return !isset($this->buffer{$this->offset}); } } [3] */ private $blockQueue; private $currentBlock = 0; /** @var Block */ private $currentBlockObject = null; private $currentDistance = 0; private $maxDistanceInt = 0; private $secondError; private $thirdError; private $secondStep; private $thirdStep; private $mainFace; private $secondFace; private $thirdFace; /** * BlockIterator constructor. * * @param Level $level * @param Vector3 $start * @param Vector3 $direction * @param int $yOffset * @param int $maxDistance */ public function __construct(Level $level, Vector3 $start, Vector3 $direction, $yOffset = 0, $maxDistance = 0){ $this->level = $level; $this->maxDistance = (int) $maxDistance; $this->blockQueue = new \SplFixedArray(3); $startClone = new Vector3($start->x, $start->y, $start->z); $startClone->y += $yOffset; $this->currentDistance = 0; $mainDirection = 0; $secondDirection = 0; $thirdDirection = 0; $mainPosition = 0; $secondPosition = 0; $thirdPosition = 0; $pos = new Vector3($startClone->x, $startClone->y, $startClone->z); $startBlock = $this->level->getBlock(new Vector3(floor($pos->x), floor($pos->y), floor($pos->z))); if($this->getXLength($direction) > $mainDirection){ $this->mainFace = $this->getXFace($direction); $mainDirection = $this->getXLength($direction); $mainPosition = $this->getXPosition($direction, $startClone, $startBlock); $this->secondFace = $this->getYFace($direction); $secondDirection = $this->getYLength($direction); $secondPosition = $this->getYPosition($direction, $startClone, $startBlock); $this->thirdFace = $this->getZFace($direction); $thirdDirection = $this->getZLength($direction); $thirdPosition = $this->getZPosition($direction, $startClone, $startBlock); } if($this->getYLength($direction) > $mainDirection){ $this->mainFace = $this->getYFace($direction); $mainDirection = $this->getYLength($direction); $mainPosition = $this->getYPosition($direction, $startClone, $startBlock); $this->secondFace = $this->getZFace($direction); $secondDirection = $this->getZLength($direction); $secondPosition = $this->getZPosition($direction, $startClone, $startBlock); $this->thirdFace = $this->getXFace($direction); $thirdDirection = $this->getXLength($direction); $thirdPosition = $this->getXPosition($direction, $startClone, $startBlock); } if($this->getZLength($direction) > $mainDirection){ $this->mainFace = $this->getZFace($direction); $mainDirection = $this->getZLength($direction); $mainPosition = $this->getZPosition($direction, $startClone, $startBlock); $this->secondFace = $this->getXFace($direction); $secondDirection = $this->getXLength($direction); $secondPosition = $this->getXPosition($direction, $startClone, $startBlock); $this->thirdFace = $this->getYFace($direction); $thirdDirection = $this->getYLength($direction); $thirdPosition = $this->getYPosition($direction, $startClone, $startBlock); } $d = $mainPosition / $mainDirection; $secondd = $secondPosition - $secondDirection * $d; $thirdd = $thirdPosition - $thirdDirection * $d; $this->secondError = floor($secondd * self::$gridSize); $this->secondStep = round($secondDirection / $mainDirection * self::$gridSize); $this->thirdError = floor($thirdd * self::$gridSize); $this->thirdStep = round($thirdDirection / $mainDirection * self::$gridSize); if($this->secondError + $this->secondStep <= 0){ $this->secondError = -$this->secondStep + 1; } if($this->thirdError + $this->thirdStep <= 0){ $this->thirdError = -$this->thirdStep + 1; } $lastBlock = $startBlock->getSide(Vector3::getOppositeSide($this->mainFace)); if($this->secondError < 0){ $this->secondError += self::$gridSize; $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->secondFace)); } if($this->thirdError < 0){ $this->thirdError += self::$gridSize; $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->thirdFace)); } $this->secondError -= self::$gridSize; $this->thirdError -= self::$gridSize; $this->blockQueue[0] = $lastBlock; $this->currentBlock = -1; $this->scan(); $startBlockFound = false; for($cnt = $this->currentBlock; $cnt >= 0; --$cnt){ if($this->blockEquals($this->blockQueue[$cnt], $startBlock)){ $this->currentBlock = $cnt; $startBlockFound = true; break; } } if(!$startBlockFound){ throw new \InvalidStateException("Start block missed in BlockIterator"); } $this->maxDistanceInt = round($maxDistance / (sqrt($mainDirection ** 2 + $secondDirection ** 2 + $thirdDirection ** 2) / $mainDirection)); } /** * @param Block $a * @param Block $b * * @return bool */ private function blockEquals(Block $a, Block $b){ return $a->x === $b->x and $a->y === $b->y and $a->z === $b->z; } /** * @param Vector3 $direction * * @return int */ private function getXFace(Vector3 $direction){ return (($direction->x) > 0) ? Vector3::SIDE_EAST : Vector3::SIDE_WEST; } /** * @param Vector3 $direction * * @return int */ private function getYFace(Vector3 $direction){ return (($direction->y) > 0) ? Vector3::SIDE_UP : Vector3::SIDE_DOWN; } /** * @param Vector3 $direction * * @return int */ private function getZFace(Vector3 $direction){ return (($direction->z) > 0) ? Vector3::SIDE_SOUTH : Vector3::SIDE_NORTH; } /** * @param Vector3 $direction * * @return number */ private function getXLength(Vector3 $direction){ return abs($direction->x); } /** * @param Vector3 $direction * * @return number */ private function getYLength(Vector3 $direction){ return abs($direction->y); } /** * @param Vector3 $direction * * @return number */ private function getZLength(Vector3 $direction){ return abs($direction->z); } /** * @param $direction * @param $position * @param $blockPosition * * @return mixed */ private function getPosition($direction, $position, $blockPosition){ return $direction > 0 ? ($position - $blockPosition) : ($blockPosition + 1 - $position); } /** * @param Vector3 $direction * @param Vector3 $position * @param Block $block * * @return mixed */ private function getXPosition(Vector3 $direction, Vector3 $position, Block $block){ return $this->getPosition($direction->x, $position->x, $block->x); } /** * @param Vector3 $direction * @param Vector3 $position * @param Block $block * * @return mixed */ private function getYPosition(Vector3 $direction, Vector3 $position, Block $block){ return $this->getPosition($direction->y, $position->y, $block->y); } /** * @param Vector3 $direction * @param Vector3 $position * @param Block $block * * @return mixed */ private function getZPosition(Vector3 $direction, Vector3 $position, Block $block){ return $this->getPosition($direction->z, $position->z, $block->z); } public function next(){ $this->scan(); if($this->currentBlock <= -1){ throw new \OutOfBoundsException; }else{ $this->currentBlockObject = $this->blockQueue[$this->currentBlock--]; } } /** * @return Block * * @throws \OutOfBoundsException */ public function current(){ if($this->currentBlockObject === null){ throw new \OutOfBoundsException; } return $this->currentBlockObject; } public function rewind(){ throw new \InvalidStateException("BlockIterator doesn't support rewind()"); } /** * @return int */ public function key(){ return $this->currentBlock - 1; } /** * @return bool */ public function valid(){ $this->scan(); return $this->currentBlock !== -1; } private function scan(){ if($this->currentBlock >= 0){ return; } if($this->maxDistance !== 0 and $this->currentDistance > $this->maxDistanceInt){ $this->end = true; return; } if($this->end){ return; } ++$this->currentDistance; $this->secondError += $this->secondStep; $this->thirdError += $this->thirdStep; if($this->secondError > 0 and $this->thirdError > 0){ $this->blockQueue[2] = $this->blockQueue[0]->getSide($this->mainFace); if(($this->secondStep * $this->thirdError) < ($this->thirdStep * $this->secondError)){ $this->blockQueue[1] = $this->blockQueue[2]->getSide($this->secondFace); $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->thirdFace); }else{ $this->blockQueue[1] = $this->blockQueue[2]->getSide($this->thirdFace); $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->secondFace); } $this->thirdError -= self::$gridSize; $this->secondError -= self::$gridSize; $this->currentBlock = 2; }elseif($this->secondError > 0){ $this->blockQueue[1] = $this->blockQueue[0]->getSide($this->mainFace); $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->secondFace); $this->secondError -= self::$gridSize; $this->currentBlock = 1; }elseif($this->thirdError > 0){ $this->blockQueue[1] = $this->blockQueue[0]->getSide($this->mainFace); $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->thirdFace); $this->thirdError -= self::$gridSize; $this->currentBlock = 1; }else{ $this->blockQueue[0] = $this->blockQueue[0]->getSide($this->mainFace); $this->currentBlock = 0; } } }getRed(); $tg += $c->getGreen(); $tb += $c->getBlue(); ++$count; } return Color::getRGB($tr / $count, $tg / $count, $tb / $count); } /** * @param $id * * @return mixed|Color */ public static function getDyeColor($id){ if(isset(self::$dyeColors[$id])){ return clone self::$dyeColors[$id]; } return Color::getRGB(0, 0, 0); } /** * Color constructor. * * @param $r * @param $g * @param $b */ public function __construct($r, $g, $b){ $this->red = $r; $this->green = $g; $this->blue = $b; } /** * @return int */ public function getRed(){ return (int) $this->red; } /** * @return int */ public function getBlue(){ return (int) $this->blue; } /** * @return int */ public function getGreen(){ return (int) $this->green; } /** * @return int */ public function getColorCode(){ return ($this->red << 16 | $this->green << 8 | $this->blue) & 0xffffff; } /** * @return string */ public function __toString(){ return "Color(red:" . $this->red . ", green:" . $this->green . ", blue:" . $this->blue . ")"; } } Config::PROPERTIES, "cnf" => Config::CNF, "conf" => Config::CNF, "config" => Config::CNF, "json" => Config::JSON, "js" => Config::JSON, "yml" => Config::YAML, "yaml" => Config::YAML, //"export" => Config::EXPORT, //"xport" => Config::EXPORT, "sl" => Config::SERIALIZED, "serialize" => Config::SERIALIZED, "txt" => Config::ENUM, "list" => Config::ENUM, "enum" => Config::ENUM, ]; /** * @param string $file Path of the file to be loaded * @param int $type Config type to load, -1 by default (detect) * @param array $default Array with the default values that will be written to the file if it did not exist * @param null &$correct Sets correct to true if everything has been loaded correctly */ public function __construct($file, $type = Config::DETECT, $default = [], &$correct = null){ $this->load($file, $type, $default); $correct = $this->correct; } /** * Removes all the changes in memory and loads the file again */ public function reload(){ $this->config = []; $this->nestedCache = []; $this->correct = false; $this->load($this->file, $this->type); } /** * @param $str * * @return mixed */ public static function fixYAMLIndexes($str){ return preg_replace("#^([ ]*)([a-zA-Z_]{1}[ ]*)\\:$#m", "$1\"$2\":", $str); } /** * @param $file * @param int $type * @param array $default * * @return bool */ public function load($file, $type = Config::DETECT, $default = []){ $this->correct = true; $this->type = (int) $type; $this->file = $file; if(!is_array($default)){ $default = []; } if(!file_exists($file)){ $this->config = $default; $this->save(); }else{ if($this->type === Config::DETECT){ $extension = explode(".", basename($this->file)); $extension = strtolower(trim(array_pop($extension))); if(isset(Config::$formats[$extension])){ $this->type = Config::$formats[$extension]; }else{ $this->correct = false; } } if($this->correct === true){ $content = file_get_contents($this->file); switch($this->type){ case Config::PROPERTIES: case Config::CNF: $this->parseProperties($content); break; case Config::JSON: $this->config = json_decode($content, true); break; case Config::YAML: $content = self::fixYAMLIndexes($content); $this->config = yaml_parse($content); break; case Config::SERIALIZED: $this->config = unserialize($content); break; case Config::ENUM: $this->parseList($content); break; default: $this->correct = false; return false; } if(!is_array($this->config)){ $this->config = $default; } if($this->fillDefaults($default, $this->config) > 0){ $this->save(); } }else{ return false; } } return true; } /** * @return bool */ public function check(){ return $this->correct === true; } /** * @param bool $async * * @return bool */ public function save($async = false){ if($this->correct === true){ try{ $content = null; switch($this->type){ case Config::PROPERTIES: case Config::CNF: $content = $this->writeProperties(); break; case Config::JSON: $content = json_encode($this->config, JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING | JSON_UNESCAPED_UNICODE); break; case Config::YAML: $content = yaml_emit($this->config, YAML_UTF8_ENCODING); break; case Config::SERIALIZED: $content = serialize($this->config); break; case Config::ENUM: $content = implode("\r\n", array_keys($this->config)); break; } if($async){ Server::getInstance()->getScheduler()->scheduleAsyncTask(new FileWriteTask($this->file, $content)); }else{ file_put_contents($this->file, $content); } }catch(\Throwable $e){ $logger = Server::getInstance()->getLogger(); $logger->critical("Could not save Config " . $this->file . ": " . $e->getMessage()); if(\pocketmine\DEBUG > 1 and $logger instanceof MainLogger){ $logger->logException($e); } } return true; }else{ return false; } } /** * @param $k * * @return bool|mixed */ public function __get($k){ return $this->get($k); } /** * @param $k * @param $v */ public function __set($k, $v){ $this->set($k, $v); } /** * @param $k * * @return bool */ public function __isset($k){ return $this->exists($k); } /** * @param $k */ public function __unset($k){ $this->remove($k); } /** * @param $key * @param $value */ public function setNested($key, $value){ $vars = explode(".", $key); $base = array_shift($vars); if(!isset($this->config[$base])){ $this->config[$base] = []; } $base =& $this->config[$base]; while(count($vars) > 0){ $baseKey = array_shift($vars); if(!isset($base[$baseKey])){ $base[$baseKey] = []; } $base =& $base[$baseKey]; } $base = $value; $this->nestedCache[$key] = $value; } /** * @param $key * @param mixed $default * * @return mixed */ public function getNested($key, $default = null){ if(isset($this->nestedCache[$key])){ return $this->nestedCache[$key]; } $vars = explode(".", $key); $base = array_shift($vars); if(isset($this->config[$base])){ $base = $this->config[$base]; }else{ return $default; } while(count($vars) > 0){ $baseKey = array_shift($vars); if(is_array($base) and isset($base[$baseKey])){ $base = $base[$baseKey]; }else{ return $default; } } return $this->nestedCache[$key] = $base; } /** * @param $k * @param mixed $default * * @return bool|mixed */ public function get($k, $default = false){ return ($this->correct and isset($this->config[$k])) ? $this->config[$k] : $default; } /** * @param string $k key to be set * @param mixed $v value to set key */ public function set($k, $v = true){ $this->config[$k] = $v; foreach($this->nestedCache as $nestedKey => $nvalue){ if(substr($nestedKey, 0, strlen($k) + 1) === ($k . ".")){ unset($this->nestedCache[$nestedKey]); } } } /** * @param array $v */ public function setAll($v){ $this->config = $v; } /** * @param $k * @param bool $lowercase If set, searches Config in single-case / lowercase. * * @return bool */ public function exists($k, $lowercase = false){ if($lowercase === true){ $k = strtolower($k); //Convert requested key to lower $array = array_change_key_case($this->config, CASE_LOWER); //Change all keys in array to lower return isset($array[$k]); //Find $k in modified array }else{ return isset($this->config[$k]); } } /** * @param $k */ public function remove($k){ unset($this->config[$k]); } /** * @param bool $keys * * @return array */ public function getAll($keys = false){ return ($keys === true ? array_keys($this->config) : $this->config); } /** * @param array $defaults */ public function setDefaults(array $defaults){ $this->fillDefaults($defaults, $this->config); } /** * @param $default * @param $data * * @return int */ private function fillDefaults($default, &$data){ $changed = 0; foreach($default as $k => $v){ if(is_array($v)){ if(!isset($data[$k]) or !is_array($data[$k])){ $data[$k] = []; } $changed += $this->fillDefaults($v, $data[$k]); }elseif(!isset($data[$k])){ $data[$k] = $v; ++$changed; } } return $changed; } /** * @param $content */ private function parseList($content){ foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){ $v = trim($v); if($v == ""){ continue; } $this->config[$v] = true; } } /** * @return string */ private function writeProperties(){ $content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n"; foreach($this->config as $k => $v){ if(is_bool($v) === true){ $v = $v === true ? "on" : "off"; }elseif(is_array($v)){ $v = implode(";", $v); } $content .= $k . "=" . $v . "\r\n"; } return $content; } /** * @param $content */ private function parseProperties($content){ if(preg_match_all('/([a-zA-Z0-9\-_\.]*)=([^\r\n]*)/u', $content, $matches) > 0){ //false or 0 matches foreach($matches[1] as $i => $k){ $v = trim($matches[2][$i]); switch(strtolower($v)){ case "on": case "true": case "yes": $v = true; break; case "off": case "false": case "no": $v = false; break; } if(isset($this->config[$k])){ MainLogger::getLogger()->debug("[Config] Repeated property " . $k . " on file " . $this->file); } $this->config[$k] = $v; } } } } shouldRecordMsg = $b; $this->lastGet = time(); } /** * @return string */ public function getMessages(){ $msg = $this->shouldSendMsg; $this->shouldSendMsg = ""; $this->lastGet = time(); return $msg; } /** * @param string $logFile * @param bool $logDebug * * @throws \RuntimeException */ public function __construct($logFile, $logDebug = false){ if(static::$logger instanceof MainLogger){ throw new \RuntimeException("MainLogger has been already created"); } static::$logger = $this; touch($logFile); $this->logFile = $logFile; $this->logDebug = (bool) $logDebug; $this->logStream = new \Threaded; $this->start(); } /** * @return MainLogger */ public static function getLogger(){ return static::$logger; } /** * @param string $message * @param string $name */ public function emergency($message, $name = "EMERGENCY"){ $this->send($message, \LogLevel::EMERGENCY, $name, TextFormat::RED); } /** * @param string $message * @param string $name */ public function alert($message, $name = "ALERT"){ $this->send($message, \LogLevel::ALERT, $name, TextFormat::RED); } /** * @param string $message * @param string $name */ public function critical($message, $name = "CRITICAL"){ $this->send($message, \LogLevel::CRITICAL, $name, TextFormat::RED); } /** * @param string $message * @param string $name */ public function error($message, $name = "ERROR"){ $this->send($message, \LogLevel::ERROR, $name, TextFormat::DARK_RED); } /** * @param string $message * @param string $name */ public function warning($message, $name = "WARNING"){ $this->send($message, \LogLevel::WARNING, $name, TextFormat::YELLOW); } /** * @param string $message * @param string $name */ public function notice($message, $name = "NOTICE"){ $this->send(TextFormat::BOLD . $message, \LogLevel::NOTICE, $name, TextFormat::AQUA); } /** * @param string $message * @param string $name */ public function info($message, $name = "INFO"){ $this->send($message, \LogLevel::INFO, $name, TextFormat::WHITE); } /** * @param string $message * @param string $name */ public function debug($message, $name = "DEBUG"){ if($this->logDebug === false){ return; } $this->send($message, \LogLevel::DEBUG, $name, TextFormat::GRAY); } /** * @param bool $logDebug */ public function setLogDebug($logDebug){ $this->logDebug = (bool) $logDebug; } /** * @param \Throwable $e * @param null $trace */ public function logException(\Throwable $e, $trace = null){ if($trace === null){ $trace = $e->getTrace(); } $errstr = $e->getMessage(); $errfile = $e->getFile(); $errno = $e->getCode(); $errline = $e->getLine(); $errorConversion = [ 0 => "EXCEPTION", E_ERROR => "E_ERROR", E_WARNING => "E_WARNING", E_PARSE => "E_PARSE", E_NOTICE => "E_NOTICE", E_CORE_ERROR => "E_CORE_ERROR", E_CORE_WARNING => "E_CORE_WARNING", E_COMPILE_ERROR => "E_COMPILE_ERROR", E_COMPILE_WARNING => "E_COMPILE_WARNING", E_USER_ERROR => "E_USER_ERROR", E_USER_WARNING => "E_USER_WARNING", E_USER_NOTICE => "E_USER_NOTICE", E_STRICT => "E_STRICT", E_RECOVERABLE_ERROR => "E_RECOVERABLE_ERROR", E_DEPRECATED => "E_DEPRECATED", E_USER_DEPRECATED => "E_USER_DEPRECATED", ]; if($errno === 0){ $type = LogLevel::CRITICAL; }else{ $type = ($errno === E_ERROR or $errno === E_USER_ERROR) ? LogLevel::ERROR : (($errno === E_USER_WARNING or $errno === E_WARNING) ? LogLevel::WARNING : LogLevel::NOTICE); } $errno = isset($errorConversion[$errno]) ? $errorConversion[$errno] : $errno; if(($pos = strpos($errstr, "\n")) !== false){ $errstr = substr($errstr, 0, $pos); } $errfile = \pocketmine\cleanPath($errfile); $this->log($type, get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline"); foreach(@\pocketmine\getTrace(1, $trace) as $i => $line){ $this->debug($line); } } /** * @param mixed $level * @param string $message */ public function log($level, $message){ switch($level){ case LogLevel::EMERGENCY: $this->emergency($message); break; case LogLevel::ALERT: $this->alert($message); break; case LogLevel::CRITICAL: $this->critical($message); break; case LogLevel::ERROR: $this->error($message); break; case LogLevel::WARNING: $this->warning($message); break; case LogLevel::NOTICE: $this->notice($message); break; case LogLevel::INFO: $this->info($message); break; case LogLevel::DEBUG: $this->debug($message); break; } } public function shutdown(){ $this->shutdown = true; } /** * @param $message * @param $level * @param $prefix * @param $color */ protected function send($message, $level, $prefix, $color){ $now = time(); $thread = \Thread::getCurrentThread(); if($thread === null){ $threadName = "Server thread"; }elseif($thread instanceof Thread or $thread instanceof Worker){ $threadName = $thread->getThreadName() . " thread"; }else{ $threadName = (new \ReflectionClass($thread))->getShortName() . " thread"; } if($this->shouldRecordMsg){ if((time() - $this->lastGet) >= 10) $this->shouldRecordMsg = false; // 10 secs timeout else{ if(strlen($this->shouldSendMsg) >= 10000) $this->shouldSendMsg = ""; $this->shouldSendMsg .= $color . "|" . $prefix . "|" . trim($message, "\r\n") . "\n"; } } $message = TextFormat::toANSI(TextFormat::AQUA . "[" . date("H:i:s", $now) . "] " . TextFormat::RESET . $color . "[" . $threadName . "/" . $prefix . "]:" . " " . $message . TextFormat::RESET); //$message = TextFormat::toANSI(TextFormat::AQUA . "[GenisysPro]->[" . date("H:i:s", $now) . "] " . TextFormat::RESET . $color . "[$prefix]:" . " " . $message . TextFormat::RESET); //$message = TextFormat::toANSI(TextFormat::AQUA . "[" . date("H:i:s") . "] ". TextFormat::RESET . $color ."<".$prefix . ">" . " " . $message . TextFormat::RESET); $cleanMessage = TextFormat::clean($message); if(!Terminal::hasFormattingCodes()){ echo $cleanMessage . PHP_EOL; }else{ echo $message . PHP_EOL; } if(isset($this->consoleCallback)){ call_user_func($this->consoleCallback); } if($this->attachment instanceof \ThreadedLoggerAttachment){ $this->attachment->call($level, $message); } $this->logStream[] = date("Y-m-d", $now) . " " . $cleanMessage . "\n"; if($this->logStream->count() === 1){ $this->synchronized(function(){ $this->notify(); }); } } /*public function run(){ $this->shutdown = false; if($this->write){ $this->logResource = fopen($this->logFile, "a+b"); if(!is_resource($this->logResource)){ throw new \RuntimeException("Couldn't open log file"); } while($this->shutdown === false){ if(!$this->write) { fclose($this->logResource); break; } $this->synchronized(function(){ while($this->logStream->count() > 0){ $chunk = $this->logStream->shift(); fwrite($this->logResource, $chunk); } $this->wait(25000); }); } if($this->logStream->count() > 0){ while($this->logStream->count() > 0){ $chunk = $this->logStream->shift(); fwrite($this->logResource, $chunk); } } fclose($this->logResource); } }*/ public function run(){ $this->shutdown = false; while($this->shutdown === false){ $this->synchronized(function(){ while($this->logStream->count() > 0){ $chunk = $this->logStream->shift(); if($this->write){ $this->logResource = file_put_contents($this->logFile, $chunk, FILE_APPEND); } } $this->wait(200000); }); } if($this->logStream->count() > 0){ while($this->logStream->count() > 0){ $chunk = $this->logStream->shift(); if($this->write){ $this->logResource = file_put_contents($this->logFile, $chunk, FILE_APPEND); } } } } /** * @param $write */ public function setWrite($write){ $this->write = $write; } /** * @param $callback */ public function setConsoleCallback($callback){ $this->consoleCallback = $callback; } } setSeed($seed); } /** * @param int $seed Integer to be used as seed. */ public function setSeed($seed){ $this->seed = $seed; $this->x = self::X ^ $seed; $this->y = self::Y ^ ($seed << 17) | (($seed >> 15) & 0x7fffffff) & 0xffffffff; $this->z = self::Z ^ ($seed << 31) | (($seed >> 1) & 0x7fffffff) & 0xffffffff; $this->w = self::W ^ ($seed << 18) | (($seed >> 14) & 0x7fffffff) & 0xffffffff; } public function getSeed(){ return $this->seed; } /** * Returns an 31-bit integer (not signed) * * @return int */ public function nextInt(){ return $this->nextSignedInt() & 0x7fffffff; } /** * Returns a 32-bit integer (signed) * * @return int */ public function nextSignedInt(){ $t = ($this->x ^ ($this->x << 11)) & 0xffffffff; $this->x = $this->y; $this->y = $this->z; $this->z = $this->w; $this->w = ($this->w ^ (($this->w >> 19) & 0x7fffffff) ^ ($t ^ (($t >> 8) & 0x7fffffff))) & 0xffffffff; return $this->w; } /** * Returns a float between 0.0 and 1.0 (inclusive) * * @return float */ public function nextFloat(){ return $this->nextInt() / 0x7fffffff; } /** * Returns a float between -1.0 and 1.0 (inclusive) * * @return float */ public function nextSignedFloat(){ return $this->nextSignedInt() / 0x7fffffff; } /** * Returns a random boolean * * @return bool */ public function nextBoolean(){ return ($this->nextSignedInt() & 0x01) === 0; } /** * Returns a random integer between $start and $end * * @param int $start default 0 * @param int $end default 0x7fffffff * * @return int */ public function nextRange($start = 0, $end = 0x7fffffff){ return $start + ($this->nextInt() % ($end + 1 - $start)); } /** * @param $bound * * @return int */ public function nextBoundedInt($bound){ return $this->nextInt() % $bound; } }minValue = $min; $this->maxValue = $max; } /** * @param int $v * * @return bool */ public function isInRange(int $v) : bool{ return $v >= $this->minValue && $v <= $this->maxValue; } }time = $time; } public function run(){ $start = time() + 1; $this->synchronized(function(){ $this->wait($this->time * 1000000); }); if(time() - $start >= $this->time){ echo "\nTook too long to stop, server was killed forcefully!\n"; @\pocketmine\kill(getmypid()); } } /** * @return string */ public function getThreadName(){ return "Server Killer"; } } 8){ self::$COLOR_BLACK = $colors >= 256 ? `tput setaf 16` : `tput setaf 0`; self::$COLOR_DARK_BLUE = $colors >= 256 ? `tput setaf 19` : `tput setaf 4`; self::$COLOR_DARK_GREEN = $colors >= 256 ? `tput setaf 34` : `tput setaf 2`; self::$COLOR_DARK_AQUA = $colors >= 256 ? `tput setaf 37` : `tput setaf 6`; self::$COLOR_DARK_RED = $colors >= 256 ? `tput setaf 124` : `tput setaf 1`; self::$COLOR_PURPLE = $colors >= 256 ? `tput setaf 127` : `tput setaf 5`; self::$COLOR_GOLD = $colors >= 256 ? `tput setaf 214` : `tput setaf 3`; self::$COLOR_GRAY = $colors >= 256 ? `tput setaf 145` : `tput setaf 7`; self::$COLOR_DARK_GRAY = $colors >= 256 ? `tput setaf 59` : `tput setaf 8`; self::$COLOR_BLUE = $colors >= 256 ? `tput setaf 63` : `tput setaf 12`; self::$COLOR_GREEN = $colors >= 256 ? `tput setaf 83` : `tput setaf 10`; self::$COLOR_AQUA = $colors >= 256 ? `tput setaf 87` : `tput setaf 14`; self::$COLOR_RED = $colors >= 256 ? `tput setaf 203` : `tput setaf 9`; self::$COLOR_LIGHT_PURPLE = $colors >= 256 ? `tput setaf 207` : `tput setaf 13`; self::$COLOR_YELLOW = $colors >= 256 ? `tput setaf 227` : `tput setaf 11`; self::$COLOR_WHITE = $colors >= 256 ? `tput setaf 231` : `tput setaf 15`; }else{ self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = `tput setaf 0`; self::$COLOR_RED = self::$COLOR_DARK_RED = `tput setaf 1`; self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = `tput setaf 2`; self::$COLOR_YELLOW = self::$COLOR_GOLD = `tput setaf 3`; self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = `tput setaf 4`; self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = `tput setaf 5`; self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = `tput setaf 6`; self::$COLOR_GRAY = self::$COLOR_WHITE = `tput setaf 7`; } } public static function init(){ if(!self::hasFormattingCodes()){ return; } switch(Utils::getOS()){ case "linux": case "mac": case "bsd": self::getEscapeCodes(); return; case "win": case "android": self::getFallbackEscapeCodes(); return; } //TODO: iOS } } $d){ if(!isset($d["text"])){ unset($newString["extra"][$k]); } } } return json_encode($newString, JSON_UNESCAPED_SLASHES); } /** * Returns an HTML-formatted string with colors/markup * * @param string|array $string * * @return string */ public static function toHTML($string){ if(!is_array($string)){ $string = self::tokenize($string); } $newString = ""; $tokens = 0; foreach($string as $token){ switch($token){ case TextFormat::BOLD: $newString .= ""; ++$tokens; break; case TextFormat::OBFUSCATED: //$newString .= ""; //++$tokens; break; case TextFormat::ITALIC: $newString .= ""; ++$tokens; break; case TextFormat::UNDERLINE: $newString .= ""; ++$tokens; break; case TextFormat::STRIKETHROUGH: $newString .= ""; ++$tokens; break; case TextFormat::RESET: $newString .= str_repeat("", $tokens); $tokens = 0; break; //Colors case TextFormat::BLACK: $newString .= ""; ++$tokens; break; case TextFormat::DARK_BLUE: $newString .= ""; ++$tokens; break; case TextFormat::DARK_GREEN: $newString .= ""; ++$tokens; break; case TextFormat::DARK_AQUA: $newString .= ""; ++$tokens; break; case TextFormat::DARK_RED: $newString .= ""; ++$tokens; break; case TextFormat::DARK_PURPLE: $newString .= ""; ++$tokens; break; case TextFormat::GOLD: $newString .= ""; ++$tokens; break; case TextFormat::GRAY: $newString .= ""; ++$tokens; break; case TextFormat::DARK_GRAY: $newString .= ""; ++$tokens; break; case TextFormat::BLUE: $newString .= ""; ++$tokens; break; case TextFormat::GREEN: $newString .= ""; ++$tokens; break; case TextFormat::AQUA: $newString .= ""; ++$tokens; break; case TextFormat::RED: $newString .= ""; ++$tokens; break; case TextFormat::LIGHT_PURPLE: $newString .= ""; ++$tokens; break; case TextFormat::YELLOW: $newString .= ""; ++$tokens; break; case TextFormat::WHITE: $newString .= ""; ++$tokens; break; default: $newString .= $token; break; } } $newString .= str_repeat("", $tokens); return $newString; } /** * Returns a string with colorized ANSI Escape codes * * @param $string * * @return string */ public static function toANSI($string){ if(!is_array($string)){ $string = self::tokenize($string); } $newString = ""; foreach($string as $token){ switch($token){ case TextFormat::BOLD: $newString .= Terminal::$FORMAT_BOLD; break; case TextFormat::OBFUSCATED: $newString .= Terminal::$FORMAT_OBFUSCATED; break; case TextFormat::ITALIC: $newString .= Terminal::$FORMAT_ITALIC; break; case TextFormat::UNDERLINE: $newString .= Terminal::$FORMAT_UNDERLINE; break; case TextFormat::STRIKETHROUGH: $newString .= Terminal::$FORMAT_STRIKETHROUGH; break; case TextFormat::RESET: $newString .= Terminal::$FORMAT_RESET; break; //Colors case TextFormat::BLACK: $newString .= Terminal::$COLOR_BLACK; break; case TextFormat::DARK_BLUE: $newString .= Terminal::$COLOR_DARK_BLUE; break; case TextFormat::DARK_GREEN: $newString .= Terminal::$COLOR_DARK_GREEN; break; case TextFormat::DARK_AQUA: $newString .= Terminal::$COLOR_DARK_AQUA; break; case TextFormat::DARK_RED: $newString .= Terminal::$COLOR_DARK_RED; break; case TextFormat::DARK_PURPLE: $newString .= Terminal::$COLOR_PURPLE; break; case TextFormat::GOLD: $newString .= Terminal::$COLOR_GOLD; break; case TextFormat::GRAY: $newString .= Terminal::$COLOR_GRAY; break; case TextFormat::DARK_GRAY: $newString .= Terminal::$COLOR_DARK_GRAY; break; case TextFormat::BLUE: $newString .= Terminal::$COLOR_BLUE; break; case TextFormat::GREEN: $newString .= Terminal::$COLOR_GREEN; break; case TextFormat::AQUA: $newString .= Terminal::$COLOR_AQUA; break; case TextFormat::RED: $newString .= Terminal::$COLOR_RED; break; case TextFormat::LIGHT_PURPLE: $newString .= Terminal::$COLOR_LIGHT_PURPLE; break; case TextFormat::YELLOW: $newString .= Terminal::$COLOR_YELLOW; break; case TextFormat::WHITE: $newString .= Terminal::$COLOR_WHITE; break; default: $newString .= $token; break; } } return $newString; } } parts[0] = (int) $part1; $this->parts[1] = (int) $part2; $this->parts[2] = (int) $part3; $this->parts[3] = (int) $part4; $this->version = $version === null ? ($this->parts[1] & 0xf000) >> 12 : (int) $version; } /** * @return int|null */ public function getVersion(){ return $this->version; } /** * @param UUID $uuid * * @return bool */ public function equals(UUID $uuid){ return $uuid->parts[0] === $this->parts[0] and $uuid->parts[1] === $this->parts[1] and $uuid->parts[2] === $this->parts[2] and $uuid->parts[3] === $this->parts[3]; } /** * Creates an UUID from an hexadecimal representation * * @param string $uuid * @param int $version * * @return UUID */ public static function fromString($uuid, $version = null){ return self::fromBinary(hex2bin(str_replace("-", "", trim($uuid))), $version); } /** * Creates an UUID from a binary representation * * @param string $uuid * @param int $version * * @return UUID */ public static function fromBinary($uuid, $version = null){ if(strlen($uuid) !== 16){ throw new \InvalidArgumentException("Must have exactly 16 bytes"); } return new UUID(Binary::readInt(substr($uuid, 0, 4)), Binary::readInt(substr($uuid, 4, 4)), Binary::readInt(substr($uuid, 8, 4)), Binary::readInt(substr($uuid, 12, 4)), $version); } /** * Creates an UUIDv3 from binary data or list of binary data * * @param array|string ...$data * * @return UUID */ public static function fromData(...$data){ $hash = hash("md5", implode($data), true); return self::fromBinary($hash, 3); } /** * @return UUID */ public static function fromRandom(){ return self::fromData(Binary::writeInt(time()), Binary::writeShort(getmypid()), Binary::writeShort(getmyuid()), Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff)), Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff))); } /** * @return string */ public function toBinary(){ return Binary::writeInt($this->parts[0]) . Binary::writeInt($this->parts[1]) . Binary::writeInt($this->parts[2]) . Binary::writeInt($this->parts[3]); } /** * @return string */ public function toString(){ $hex = bin2hex(self::toBinary()); //xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx 8-4-4-12 if($this->version !== null){ return substr($hex, 0, 8) . "-" . substr($hex, 8, 4) . "-" . hexdec($this->version) . substr($hex, 13, 3) . "-8" . substr($hex, 17, 3) . "-" . substr($hex, 20, 12); } return substr($hex, 0, 8) . "-" . substr($hex, 8, 4) . "-" . substr($hex, 12, 4) . "-" . substr($hex, 16, 4) . "-" . substr($hex, 20, 12); } /** * @return string */ public function __toString(){ return $this->toString(); } /** * @param int $partNumber * * @return mixed */ public function getPart(int $partNumber){ if($partNumber < 0 or $partNumber > 3){ throw new \InvalidArgumentException("Invalid UUID part index $partNumber"); } return $this->parts[$partNumber]; } /** * @return array */ public function getParts() : array{ return $this->parts; } } $v){ if($v == "00-00-00-00-00-00"){ unset($matches[1][$i]); } } $machine .= implode(" ", $matches[1]); //Mac Addresses } }elseif($os === "linux"){ if(file_exists("/etc/machine-id")){ $machine .= file_get_contents("/etc/machine-id"); }else{ @exec("ifconfig", $mac); $mac = implode("\n", $mac); if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches)){ foreach($matches[1] as $i => $v){ if($v == "00:00:00:00:00:00"){ unset($matches[1][$i]); } } $machine .= implode(" ", $matches[1]); //Mac Addresses } } }elseif($os === "android"){ $machine .= @file_get_contents("/system/build.prop"); }elseif($os === "mac"){ $machine .= `system_profiler SPHardwareDataType | grep UUID`; } $data = $machine . PHP_MAXPATHLEN; $data .= PHP_INT_MAX; $data .= PHP_INT_SIZE; $data .= get_current_user(); foreach(get_loaded_extensions() as $ext){ $data .= $ext . ":" . phpversion($ext); } $uuid = UUID::fromData($machine, $data); if($extra === ""){ self::$serverUniqueId = $uuid; } return $uuid; } /** * Gets the External IP using an external service, it is cached * * @param bool $force default false, force IP check even when cached * * @return string */ public static function getIP($force = false){ if(Utils::$online === false){ return false; }elseif(Utils::$ip !== false and $force !== true){ return Utils::$ip; } $ip = trim(strip_tags(Utils::getURL("https://api.ipify.org"))); if($ip){ Utils::$ip = $ip; }else{ $ip = Utils::getURL("http://www.checkip.org/"); if(preg_match('#">([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){ Utils::$ip = $matches[1]; }else{ $ip = Utils::getURL("http://checkmyip.org/"); if(preg_match('#Your IP address is ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){ Utils::$ip = $matches[1]; }else{ $ip = trim(Utils::getURL("http://ifconfig.me/ip")); if($ip != ""){ Utils::$ip = $ip; }else{ return false; } } } } return Utils::$ip; } /** * Returns the current Operating System * Windows => win * MacOS => mac * iOS => ios * Android => android * Linux => Linux * BSD => bsd * Other => other * * @param bool $recalculate * * @return string */ public static function getOS($recalculate = false){ if(self::$os === null or $recalculate){ $uname = php_uname("s"); if(stripos($uname, "Darwin") !== false){ if(strpos(php_uname("m"), "iP") === 0){ self::$os = "ios"; }else{ self::$os = "mac"; } }elseif(stripos($uname, "Win") !== false or $uname === "Msys"){ self::$os = "win"; }elseif(stripos($uname, "Linux") !== false){ if(@file_exists("/system/build.prop")){ self::$os = "android"; }else{ self::$os = "linux"; } }elseif(stripos($uname, "BSD") !== false or $uname === "DragonFly"){ self::$os = "bsd"; }else{ self::$os = "other"; } } return self::$os; } /** * @return array */ public static function getRealMemoryUsage(){ $stack = 0; $heap = 0; if(Utils::getOS() === "linux" or Utils::getOS() === "android"){ $mappings = file("/proc/self/maps"); foreach($mappings as $line){ if(preg_match("#([a-z0-9]+)\\-([a-z0-9]+) [rwxp\\-]{4} [a-z0-9]+ [^\\[]*\\[([a-zA-z0-9]+)\\]#", trim($line), $matches) > 0){ if(strpos($matches[3], "heap") === 0){ $heap += hexdec($matches[2]) - hexdec($matches[1]); }elseif(strpos($matches[3], "stack") === 0){ $stack += hexdec($matches[2]) - hexdec($matches[1]); } } } } return [$heap, $stack]; } /** * @param bool $advanced * * @return array|int|null */ public static function getMemoryUsage($advanced = false){ $reserved = memory_get_usage(); $VmSize = null; $VmRSS = null; if(Utils::getOS() === "linux" or Utils::getOS() === "android"){ $status = file_get_contents("/proc/self/status"); if(preg_match("/VmRSS:[ \t]+([0-9]+) kB/", $status, $matches) > 0){ $VmRSS = $matches[1] * 1024; } if(preg_match("/VmSize:[ \t]+([0-9]+) kB/", $status, $matches) > 0){ $VmSize = $matches[1] * 1024; } } //TODO: more OS if($VmRSS === null){ $VmRSS = memory_get_usage(); } if(!$advanced){ return $VmRSS; } if($VmSize === null){ $VmSize = memory_get_usage(true); } return [$reserved, $VmRSS, $VmSize]; } /** * @return int */ public static function getThreadCount(){ if(Utils::getOS() === "linux" or Utils::getOS() === "android"){ if(preg_match("/Threads:[ \t]+([0-9]+)/", file_get_contents("/proc/self/status"), $matches) > 0){ return (int) $matches[1]; } } //TODO: more OS return count(ThreadManager::getInstance()->getAll()) + 3; //RakLib + MainLogger + Main Thread } /** * @param bool $recalculate * * @return int */ public static function getCoreCount($recalculate = false){ static $processors = 0; if($processors > 0 and !$recalculate){ return $processors; }else{ $processors = 0; } switch(Utils::getOS()){ case "linux": case "android": if(file_exists("/proc/cpuinfo")){ foreach(file("/proc/cpuinfo") as $l){ if(preg_match('/^processor[ \t]*:[ \t]*[0-9]+$/m', $l) > 0){ ++$processors; } } }else{ if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim(@file_get_contents("/sys/devices/system/cpu/present")), $matches) > 0){ $processors = (int) ($matches[2] - $matches[1]); } } break; case "bsd": case "mac": $processors = (int) `sysctl -n hw.ncpu`; $processors = (int) `sysctl -n hw.ncpu`; break; case "win": $processors = (int) getenv("NUMBER_OF_PROCESSORS"); break; } return $processors; } /** * Returns a prettified hexdump * * @param string $bin * * @return string */ public static function hexdump($bin){ $output = ""; $bin = str_split($bin, 16); foreach($bin as $counter => $line){ $hex = chunk_split(chunk_split(str_pad(bin2hex($line), 32, " ", STR_PAD_RIGHT), 2, " "), 24, " "); $ascii = preg_replace('#([^\x20-\x7E])#', ".", $line); $output .= str_pad(dechex($counter << 4), 4, "0", STR_PAD_LEFT) . " " . $hex . " " . $ascii . PHP_EOL; } return $output; } /** * Returns a string that can be printed, replaces non-printable characters * * @param $str * * @return string */ public static function printable($str){ if(!is_string($str)){ return gettype($str); } return preg_replace('#([^\x20-\x7E])#', '.', $str); } /* public static function angle3D($pos1, $pos2){ $X = $pos1["x"] - $pos2["x"]; $Z = $pos1["z"] - $pos2["z"]; $dXZ = sqrt(pow($X, 2) + pow($Z, 2)); $Y = $pos1["y"] - $pos2["y"]; $hAngle = rad2deg(atan2($Z, $X) - M_PI_2); $vAngle = rad2deg(-atan2($Y, $dXZ)); return array("yaw" => $hAngle, "pitch" => $vAngle); }*/ /** * GETs an URL using cURL * * @param $page * @param int $timeout default 10 * @param array $extraHeaders * * @return bool|mixed */ public static function getURL($page, $timeout = 10, array $extraHeaders = []){ if(Utils::$online === false){ return false; } $ch = curl_init($page); curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 PocketMine-MP"], $extraHeaders)); curl_setopt($ch, CURLOPT_AUTOREFERER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, (int) $timeout); $ret = curl_exec($ch); curl_close($ch); return $ret; } /** * POSTs data to an URL * * @param $page * @param array|string $args * @param int $timeout * @param array $extraHeaders * * @return bool|mixed */ public static function postURL($page, $args, $timeout = 10, array $extraHeaders = []){ if(Utils::$online === false){ return false; } $ch = curl_init($page); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $args); curl_setopt($ch, CURLOPT_AUTOREFERER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 PocketMine-MP"], $extraHeaders)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, (int) $timeout); $ret = curl_exec($ch); curl_close($ch); return $ret; } /** * @param $string * * @return int */ public static function javaStringHash($string){ $hash = 0; for($i = 0; $i < strlen($string); $i++){ $ord = ord($string{$i}); if($ord & 0x80){ $ord -= 0x100; } $hash = 31 * $hash + $ord; while($hash > 0x7FFFFFFF){ $hash -= 0x100000000; } while($hash < -0x80000000){ $hash += 0x100000000; } $hash &= 0xFFFFFFFF; } return $hash; } } [3] */ private $positionQueue; private $currentBlock = 0; /** @var Vector3 */ private $currentBlockObject = null; private $currentDistance = 0; private $maxDistanceInt = 0; private $secondError; private $thirdError; private $secondStep; private $thirdStep; private $mainFace; private $secondFace; private $thirdFace; /** * VectorIterator constructor. * * @param ChunkManager $level * @param Vector3 $from * @param Vector3 $to */ public function __construct(ChunkManager $level, Vector3 $from, Vector3 $to){ if($from->equals($to)){ $this->end = true; $this->currentBlock = -1; return; } $direction = $to->subtract($from)->normalize(); $maxDistance = $from->distance($to); $this->level = $level; $this->maxDistance = (int) $maxDistance; $this->positionQueue = new \SplFixedArray(3); $startClone = new Vector3($from->x, $from->y, $from->z); $this->currentDistance = 0; $mainDirection = 0; $secondDirection = 0; $thirdDirection = 0; $mainPosition = 0; $secondPosition = 0; $thirdPosition = 0; $pos = new Vector3($startClone->x, $startClone->y, $startClone->z); $startBlock = new Vector3(floor($pos->x), floor($pos->y), floor($pos->z)); if($this->getXLength($direction) > $mainDirection){ $this->mainFace = $this->getXFace($direction); $mainDirection = $this->getXLength($direction); $mainPosition = $this->getXPosition($direction, $startClone, $startBlock); $this->secondFace = $this->getYFace($direction); $secondDirection = $this->getYLength($direction); $secondPosition = $this->getYPosition($direction, $startClone, $startBlock); $this->thirdFace = $this->getZFace($direction); $thirdDirection = $this->getZLength($direction); $thirdPosition = $this->getZPosition($direction, $startClone, $startBlock); } if($this->getYLength($direction) > $mainDirection){ $this->mainFace = $this->getYFace($direction); $mainDirection = $this->getYLength($direction); $mainPosition = $this->getYPosition($direction, $startClone, $startBlock); $this->secondFace = $this->getZFace($direction); $secondDirection = $this->getZLength($direction); $secondPosition = $this->getZPosition($direction, $startClone, $startBlock); $this->thirdFace = $this->getXFace($direction); $thirdDirection = $this->getXLength($direction); $thirdPosition = $this->getXPosition($direction, $startClone, $startBlock); } if($this->getZLength($direction) > $mainDirection){ $this->mainFace = $this->getZFace($direction); $mainDirection = $this->getZLength($direction); $mainPosition = $this->getZPosition($direction, $startClone, $startBlock); $this->secondFace = $this->getXFace($direction); $secondDirection = $this->getXLength($direction); $secondPosition = $this->getXPosition($direction, $startClone, $startBlock); $this->thirdFace = $this->getYFace($direction); $thirdDirection = $this->getYLength($direction); $thirdPosition = $this->getYPosition($direction, $startClone, $startBlock); } $d = $mainPosition / $mainDirection; $secondd = $secondPosition - $secondDirection * $d; $thirdd = $thirdPosition - $thirdDirection * $d; $this->secondError = floor($secondd * self::$gridSize); $this->secondStep = round($secondDirection / $mainDirection * self::$gridSize); $this->thirdError = floor($thirdd * self::$gridSize); $this->thirdStep = round($thirdDirection / $mainDirection * self::$gridSize); if($this->secondError + $this->secondStep <= 0){ $this->secondError = -$this->secondStep + 1; } if($this->thirdError + $this->thirdStep <= 0){ $this->thirdError = -$this->thirdStep + 1; } $lastBlock = $startBlock->getSide(Vector3::getOppositeSide($this->mainFace)); if($this->secondError < 0){ $this->secondError += self::$gridSize; $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->secondFace)); } if($this->thirdError < 0){ $this->thirdError += self::$gridSize; $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->thirdFace)); } $this->secondError -= self::$gridSize; $this->thirdError -= self::$gridSize; $this->positionQueue[0] = $lastBlock; $this->currentBlock = -1; $this->scan(); $startBlockFound = false; for($cnt = $this->currentBlock; $cnt >= 0; --$cnt){ if($this->posEquals($this->positionQueue[$cnt], $startBlock)){ $this->currentBlock = $cnt; $startBlockFound = true; break; } } if(!$startBlockFound){ throw new \InvalidStateException("Start block missed in BlockIterator"); } $this->maxDistanceInt = round($maxDistance / (sqrt($mainDirection ** 2 + $secondDirection ** 2 + $thirdDirection ** 2) / $mainDirection)); } /** * @param Vector3 $a * @param Vector3 $b * * @return bool */ private function posEquals(Vector3 $a, Vector3 $b){ return $a->x === $b->x and $a->y === $b->y and $a->z === $b->z; } /** * @param Vector3 $direction * * @return int */ private function getXFace(Vector3 $direction){ return (($direction->x) > 0) ? Vector3::SIDE_EAST : Vector3::SIDE_WEST; } /** * @param Vector3 $direction * * @return int */ private function getYFace(Vector3 $direction){ return (($direction->y) > 0) ? Vector3::SIDE_UP : Vector3::SIDE_DOWN; } /** * @param Vector3 $direction * * @return int */ private function getZFace(Vector3 $direction){ return (($direction->z) > 0) ? Vector3::SIDE_SOUTH : Vector3::SIDE_NORTH; } /** * @param Vector3 $direction * * @return number */ private function getXLength(Vector3 $direction){ return abs($direction->x); } /** * @param Vector3 $direction * * @return number */ private function getYLength(Vector3 $direction){ return abs($direction->y); } /** * @param Vector3 $direction * * @return number */ private function getZLength(Vector3 $direction){ return abs($direction->z); } /** * @param $direction * @param $position * @param $blockPosition * * @return mixed */ private function getPosition($direction, $position, $blockPosition){ return $direction > 0 ? ($position - $blockPosition) : ($blockPosition + 1 - $position); } /** * @param Vector3 $direction * @param Vector3 $position * @param Vector3 $block * * @return mixed */ private function getXPosition(Vector3 $direction, Vector3 $position, Vector3 $block){ return $this->getPosition($direction->x, $position->x, $block->x); } /** * @param Vector3 $direction * @param Vector3 $position * @param Vector3 $block * * @return mixed */ private function getYPosition(Vector3 $direction, Vector3 $position, Vector3 $block){ return $this->getPosition($direction->y, $position->y, $block->y); } /** * @param Vector3 $direction * @param Vector3 $position * @param Vector3 $block * * @return mixed */ private function getZPosition(Vector3 $direction, Vector3 $position, Vector3 $block){ return $this->getPosition($direction->z, $position->z, $block->z); } public function next(){ $this->scan(); if($this->currentBlock <= -1){ throw new \OutOfBoundsException; }else{ $this->currentBlockObject = $this->positionQueue[$this->currentBlock--]; } } /** * @return Block * * @throws \OutOfBoundsException */ public function current(){ if($this->currentBlockObject === null){ throw new \OutOfBoundsException; } return $this->currentBlockObject; } public function rewind(){ throw new \InvalidStateException("BlockIterator doesn't support rewind()"); } /** * @return int */ public function key(){ return $this->currentBlock - 1; } /** * @return bool */ public function valid(){ $this->scan(); return $this->currentBlock !== -1; } private function scan(){ if($this->currentBlock >= 0){ return; } if($this->maxDistance !== 0 and $this->currentDistance > $this->maxDistanceInt){ $this->end = true; return; } if($this->end){ return; } ++$this->currentDistance; $this->secondError += $this->secondStep; $this->thirdError += $this->thirdStep; if($this->secondError > 0 and $this->thirdError > 0){ $this->positionQueue[2] = $this->positionQueue[0]->getSide($this->mainFace); if(($this->secondStep * $this->thirdError) < ($this->thirdStep * $this->secondError)){ $this->positionQueue[1] = $this->positionQueue[2]->getSide($this->secondFace); $this->positionQueue[0] = $this->positionQueue[1]->getSide($this->thirdFace); }else{ $this->positionQueue[1] = $this->positionQueue[2]->getSide($this->thirdFace); $this->positionQueue[0] = $this->positionQueue[1]->getSide($this->secondFace); } $this->thirdError -= self::$gridSize; $this->secondError -= self::$gridSize; $this->currentBlock = 2; }elseif($this->secondError > 0){ $this->positionQueue[1] = $this->positionQueue[0]->getSide($this->mainFace); $this->positionQueue[0] = $this->positionQueue[1]->getSide($this->secondFace); $this->secondError -= self::$gridSize; $this->currentBlock = 1; }elseif($this->thirdError > 0){ $this->positionQueue[1] = $this->positionQueue[0]->getSide($this->mainFace); $this->positionQueue[0] = $this->positionQueue[1]->getSide($this->thirdFace); $this->thirdError -= self::$gridSize; $this->currentBlock = 1; }else{ $this->positionQueue[0] = $this->positionQueue[0]->getSide($this->mainFace); $this->currentBlock = 0; } } }minor = $version & 0x1F; $this->major = ($version >> 5) & 0x0F; $this->generation = ($version >> 9) & 0x0F; }else{ $this->generation = 0; $this->major = 0; $this->minor = 0; $this->development = true; $this->build = 0; } } /** * @return int */ public function getNumber(){ return (int) (($this->generation << 9) + ($this->major << 5) + $this->minor); } /** * @deprecated */ public function getStage(){ return "final"; } /** * @return int */ public function getGeneration(){ return $this->generation; } /** * @return int */ public function getMajor(){ return $this->major; } /** * @return int */ public function getMinor(){ return $this->minor; } /** * @return string */ public function getRelease(){ return $this->generation . "." . $this->major . ($this->minor > 0 ? "." . $this->minor : ""); } /** * @return int */ public function getBuild(){ return $this->build; } /** * @return bool */ public function isDev(){ return $this->development === true; } /** * @param bool $build * * @return string */ public function get($build = false){ return $this->getRelease() . ($this->development === true ? "dev" : "") . (($this->build > 0 and $build === true) ? "-" . $this->build : ""); } /** * @return string */ public function __toString(){ return $this->get(); } /** * @param $target * @param bool $diff * * @return int */ public function compare($target, $diff = false){ if(($target instanceof VersionString) === false){ $target = new VersionString($target); } $number = $this->getNumber(); $tNumber = $target->getNumber(); if($diff === true){ return $tNumber - $number; } if($number > $tNumber){ return -1; //Target is older }elseif($number < $tNumber){ return 1; //Target is newer }elseif($target->getBuild() > $this->getBuild()){ return 1; }elseif($target->getBuild() < $this->getBuild()){ return -1; }else{ return 0; //Same version } } } $native){ echo " $native => $short\n"; } do{ echo "[?] Language (eng): "; $lang = strtolower($this->getInput("eng")); if(!isset(InstallerLang::$languages[$lang])){ echo "[!] Couldn't find the language\n"; $lang = false; } $this->defaultLang = $lang; }while($lang == false); $this->lang = new InstallerLang($lang); echo "[*] " . $this->lang->get("language_has_been_selected") . "\n"; $this->relayLangSetting(); if(!$this->showLicense()){ return false; } echo "[?] " . $this->lang->get("skip_installer") . " (y/N): "; if(strtolower($this->getInput()) === "y"){ return true; } echo "\n"; $this->welcome(); $this->generateBaseConfig(); $this->generateUserFiles(); $this->networkFunctions(); $this->endWizard(); return true; } public function getDefaultLang(){ return $this->defaultLang; } /** * @return bool */ private function showLicense(){ echo $this->lang->welcome_to_pocketmine . "\n"; echo <<lang->accept_license . " (y/N): "; if(strtolower($this->getInput("n")) != "y"){ echo "[!] " . $this->lang->you_have_to_accept_the_license . "\n"; sleep(5); return false; } return true; } private function welcome(){ echo "[*] " . $this->lang->setting_up_server_now . "\n"; echo "[*] " . $this->lang->default_values_info . "\n"; echo "[*] " . $this->lang->server_properties . "\n"; } private function generateBaseConfig(){ $config = new Config(\pocketmine\DATA . "server.properties", Config::PROPERTIES); echo "[?] " . $this->lang->name_your_server . " (" . self::DEFAULT_NAME . "): "; $server_name = $this->getInput(self::DEFAULT_NAME); $config->set("server-name", $server_name); $config->set("motd", $server_name); //MOTD is now used as server name echo "[*] " . $this->lang->port_warning . "\n"; do{ echo "[?] " . $this->lang->server_port . " (" . self::DEFAULT_PORT . "): "; $port = (int) $this->getInput(self::DEFAULT_PORT); if($port <= 0 or $port > 65535){ echo "[!] " . $this->lang->invalid_port . "\n"; } }while($port <= 0 or $port > 65535); $config->set("server-port", $port); echo "[*] " . $this->lang->online_mode_info . "\n"; echo "[?] " . $this->lang->online_mode . " (y/N): "; $config->set("online-mode", strtolower($this->getInput("y")) == "y"); echo "[?] " . $this->lang->level_name . " (" . self::DEFAULT_LEVEL_NAME . "): "; $config->set("level-name", $this->getInput(self::DEFAULT_LEVEL_NAME)); do{ echo "[?] " . $this->lang->level_type . " (" . self::DEFAULT_LEVEL_TYPE . "): "; $type = strtoupper((string) $this->getInput(self::DEFAULT_LEVEL_TYPE)); if(!in_array($type, self::LEVEL_TYPES)){ echo "[!] " . $this->lang->invalid_level_type . "\n"; } }while(!in_array($type, self::LEVEL_TYPES)); $config->set("level-type", $type); /*echo "[*] " . $this->lang->ram_warning . "\n"; echo "[?] " . $this->lang->server_ram . " (" . self::DEFAULT_MEMORY . "): "; $config->set("memory-limit", ((int) $this->getInput(self::DEFAULT_MEMORY)) . "M");*/ echo "[*] " . $this->lang->gamemode_info . "\n"; do{ echo "[?] " . $this->lang->default_gamemode . ": (" . self::DEFAULT_GAMEMODE . "): "; $gamemode = (int) $this->getInput(self::DEFAULT_GAMEMODE); }while($gamemode < 0 or $gamemode > 3); $config->set("gamemode", $gamemode); echo "[?] " . $this->lang->max_players . " (" . self::DEFAULT_PLAYERS . "): "; $config->set("max-players", (int) $this->getInput(self::DEFAULT_PLAYERS)); echo "[*] " . $this->lang->spawn_protection_info . "\n"; echo "[?] " . $this->lang->spawn_protection . " (Y/n): "; if(strtolower($this->getInput("y")) == "n"){ $config->set("spawn-protection", -1); }else{ $config->set("spawn-protection", 16); } echo "[?] " . $this->lang->announce_player_achievements . " (y/N): "; if(strtolower($this->getInput("n")) === "y"){ $config->set("announce-player-achievements", "on"); }else{ $config->set("announce-player-achievements", "off"); } $config->save(); } private function generateUserFiles(){ echo "[*] " . $this->lang->op_info . "\n"; echo "[?] " . $this->lang->op_who . ": "; $op = strtolower($this->getInput("")); if($op === ""){ echo "[!] " . $this->lang->op_warning . "\n"; }else{ $ops = new Config(\pocketmine\DATA . "ops.txt", Config::ENUM); $ops->set($op, true); $ops->save(); } echo "[*] " . $this->lang->whitelist_info . "\n"; echo "[?] " . $this->lang->whitelist_enable . " (y/N): "; $config = new Config(\pocketmine\DATA . "server.properties", Config::PROPERTIES); if(strtolower($this->getInput("n")) === "y"){ echo "[!] " . $this->lang->whitelist_warning . "\n"; $config->set("white-list", true); }else{ $config->set("white-list", false); } $config->save(); } private function networkFunctions(){ $config = new Config(\pocketmine\DATA . "server.properties", Config::PROPERTIES); echo "[!] " . $this->lang->query_warning1 . "\n"; echo "[!] " . $this->lang->query_warning2 . "\n"; echo "[?] " . $this->lang->query_disable . " (y/N): "; if(strtolower($this->getInput("n")) === "y"){ $config->set("enable-query", false); }else{ $config->set("enable-query", true); } echo "[*] " . $this->lang->rcon_info . "\n"; echo "[?] " . $this->lang->rcon_enable . " (y/N): "; if(strtolower($this->getInput("n")) === "y"){ $config->set("enable-rcon", true); $password = substr(base64_encode(random_bytes(20)), 3, 10); $config->set("rcon.password", $password); echo "[*] " . $this->lang->rcon_password . ": " . $password . "\n"; }else{ $config->set("enable-rcon", false); } /*echo "[*] " . $this->lang->usage_info . "\n"; echo "[?] " . $this->lang->usage_disable . " (y/N): "; if(strtolower($this->getInput("n")) === "y"){ $config->set("send-usage", false); }else{ $config->set("send-usage", true); }*/ $config->save(); echo "[*] " . $this->lang->ip_get . "\n"; $externalIP = Utils::getIP(); $internalIP = gethostbyname(trim(`hostname`)); echo "[!] " . $this->lang->get("ip_warning", ["{{EXTERNAL_IP}}", "{{INTERNAL_IP}}"], [$externalIP, $internalIP]) . "\n"; echo "[!] " . $this->lang->ip_confirm; $this->getInput(); } private function relayLangSetting(){ if(file_exists(\pocketmine\DATA . "lang.txt")){ unlink(\pocketmine\DATA . "lang.txt"); } $langFile = new Config(\pocketmine\DATA . "lang.txt", Config::ENUM); $langFile->set($this->defaultLang, true); $langFile->save(); } private function endWizard(){ echo "[*] " . $this->lang->you_have_finished . "\n"; echo "[*] " . $this->lang->pocketmine_plugins . "\n"; echo "[*] " . $this->lang->pocketmine_will_start . "\n\n\n"; sleep(4); } /** * @param string $default * * @return string */ private function getInput($default = ""){ $input = trim(fgets(STDIN)); return $input === "" ? $default : $input; } } "English", "chs" => "简体中文", "zho" => "繁體中文", "jpn" => "日本語", "rus" => "Русский", "ita" => "Italiano", "kor" => "한국어", "deu" => "Deutsch", "fra" => "Français", "ind" => "Bahasa Indonesia", "ukr" => "Україна" ]; private $texts = []; private $lang; private $langfile; /** * InstallerLang constructor. * * @param string $lang */ public function __construct($lang = ""){ if(file_exists(\pocketmine\PATH . "src/pocketmine/lang/Installer/" . $lang . ".ini")){ $this->lang = $lang; $this->langfile = \pocketmine\PATH . "src/pocketmine/lang/Installer/" . $lang . ".ini"; }else{ $files = []; foreach(new \DirectoryIterator(\pocketmine\PATH . "src/pocketmine/lang/Installer/") as $file){ if($file->getExtension() === "ini" and substr($file->getFilename(), 0, 2) === $lang){ $files[$file->getFilename()] = $file->getSize(); } } if(count($files) > 0){ arsort($files); reset($files); $l = key($files); $l = substr($l, 0, -4); $this->lang = isset(self::$languages[$l]) ? $l : $lang; $this->langfile = \pocketmine\PATH . "src/pocketmine/lang/Installer/" . $l . ".ini"; }else{ $this->lang = "en"; $this->langfile = \pocketmine\PATH . "src/pocketmine/lang/Installer/eng.ini"; } } $this->loadLang(\pocketmine\PATH . "src/pocketmine/lang/Installer/eng.ini", "eng"); if($this->lang !== "en"){ $this->loadLang($this->langfile, $this->lang); } } /** * @return string */ public function getLang(){ return ($this->lang); } /** * @param $langfile * @param string $lang */ public function loadLang($langfile, $lang = "en"){ $this->texts[$lang] = []; $texts = explode("\n", str_replace(["\r", "\\/\\/"], ["", "//"], file_get_contents($langfile))); foreach($texts as $line){ $line = trim($line); if($line === ""){ continue; } $line = explode("=", $line); $this->texts[$lang][trim(array_shift($line))] = trim(str_replace(["\\n", "\\N",], "\n", implode("=", $line))); } } /** * @param $name * @param array $search * @param array $replace * * @return mixed */ public function get($name, $search = [], $replace = []){ if(!isset($this->texts[$this->lang][$name])){ if($this->lang !== "en" and isset($this->texts["en"][$name])){ return $this->texts["en"][$name]; }else{ return $name; } }elseif(count($search) > 0){ return str_replace($search, $replace, $this->texts[$this->lang][$name]); }else{ return $this->texts[$this->lang][$name]; } } /** * @param $name * * @return mixed */ public function __get($name){ return $this->get($name); } } > 56; }else{ return $b << 24 >> 24; } }else{ return $b; } } /** * Writes an unsigned/signed byte * * @param $c * * @return string */ public static function writeByte($c){ return chr($c); } /** * Reads a 16-bit unsigned big-endian number * * @param $str * * @return int */ public static function readShort($str){ return unpack("n", $str)[1]; } /** * Reads a 16-bit signed big-endian number * * @param $str * * @return int */ public static function readSignedShort($str){ if(PHP_INT_SIZE === 8){ return unpack("n", $str)[1] << 48 >> 48; }else{ return unpack("n", $str)[1] << 16 >> 16; } } /** * Writes a 16-bit signed/unsigned big-endian number * * @param $value * * @return string */ public static function writeShort($value){ return pack("n", $value); } /** * Reads a 16-bit signed/unsigned little-endian number * * @param $str * @param bool $signed * * @return int */ public static function readLShort($str, $signed = true){ $unpacked = unpack("v", $str)[1]; if($signed){ if(PHP_INT_SIZE === 8){ return $unpacked << 48 >> 48; }else{ return $unpacked << 16 >> 16; } }else{ return $unpacked; } } /** * Writes a 16-bit signed/unsigned little-endian number * * @param $value * * @return string */ public static function writeLShort($value){ return pack("v", $value); } public static function readInt($str){ if(PHP_INT_SIZE === 8){ return unpack("N", $str)[1] << 32 >> 32; }else{ return unpack("N", $str)[1]; } } public static function writeInt($value){ return pack("N", $value); } public static function readLInt($str){ if(PHP_INT_SIZE === 8){ return unpack("V", $str)[1] << 32 >> 32; }else{ return unpack("V", $str)[1]; } } public static function writeLInt($value){ return pack("V", $value); } public static function readFloat($str){ return ENDIANNESS === self::BIG_ENDIAN ? unpack("f", $str)[1] : unpack("f", strrev($str))[1]; } public static function writeFloat($value){ return ENDIANNESS === self::BIG_ENDIAN ? pack("f", $value) : strrev(pack("f", $value)); } public static function readLFloat($str){ return ENDIANNESS === self::BIG_ENDIAN ? unpack("f", strrev($str))[1] : unpack("f", $str)[1]; } public static function writeLFloat($value){ return ENDIANNESS === self::BIG_ENDIAN ? strrev(pack("f", $value)) : pack("f", $value); } public static function readDouble($str){ return ENDIANNESS === self::BIG_ENDIAN ? unpack("d", $str)[1] : unpack("d", strrev($str))[1]; } public static function writeDouble($value){ return ENDIANNESS === self::BIG_ENDIAN ? pack("d", $value) : strrev(pack("d", $value)); } public static function readLDouble($str){ return ENDIANNESS === self::BIG_ENDIAN ? unpack("d", strrev($str))[1] : unpack("d", $str)[1]; } public static function writeLDouble($value){ return ENDIANNESS === self::BIG_ENDIAN ? strrev(pack("d", $value)) : pack("d", $value); } public static function readLong($x){ if(PHP_INT_SIZE === 8){ list(, $int1, $int2) = unpack("N*", $x); return ($int1 << 32) | $int2; }else{ $value = "0"; for($i = 0; $i < 8; $i += 2){ $value = bcmul($value, "65536", 0); $value = bcadd($value, self::readShort(substr($x, $i, 2)), 0); } if(bccomp($value, "9223372036854775807") == 1){ $value = bcadd($value, "-18446744073709551616"); } return $value; } } public static function writeLong($value){ if(PHP_INT_SIZE === 8){ return pack("NN", $value >> 32, $value & 0xFFFFFFFF); }else{ $x = ""; if(bccomp($value, "0") == -1){ $value = bcadd($value, "18446744073709551616"); } $x .= self::writeShort(bcmod(bcdiv($value, "281474976710656"), "65536")); $x .= self::writeShort(bcmod(bcdiv($value, "4294967296"), "65536")); $x .= self::writeShort(bcmod(bcdiv($value, "65536"), "65536")); $x .= self::writeShort(bcmod($value, "65536")); return $x; } } public static function readLLong($str){ return self::readLong(strrev($str)); } public static function writeLLong($value){ return strrev(self::writeLong($value)); } } 0){ echo "[CRITICAL] Use PHP >= 7.0" . PHP_EOL; ++$errors; } if(!extension_loaded("sockets")){ echo "[CRITICAL] Unable to find the Socket extension." . PHP_EOL; ++$errors; } if(!extension_loaded("pthreads")){ echo "[CRITICAL] Unable to find the pthreads extension." . PHP_EOL; ++$errors; }else{ $pthreads_version = phpversion("pthreads"); if(substr_count($pthreads_version, ".") < 2){ $pthreads_version = "0.$pthreads_version"; } if(version_compare($pthreads_version, "3.0.0") < 0){ echo "[CRITICAL] pthreads >= 3.0.0 is required, while you have $pthreads_version."; ++$errors; } } if($errors > 0){ exit(1); //Exit with error } unset($errors); abstract class RakLib{ const VERSION = "0.8.0"; const PROTOCOL = 6; const MAGIC = "\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x12\x34\x56\x78"; const PRIORITY_NORMAL = 0; const PRIORITY_IMMEDIATE = 1; const FLAG_NEED_ACK = 0b00001000; /* * Internal Packet: * int32 (length without this field) * byte (packet ID) * payload */ /* * ENCAPSULATED payload: * byte (identifier length) * byte[] (identifier) * byte (flags, last 3 bits, priority) * payload (binary internal EncapsulatedPacket) */ const PACKET_ENCAPSULATED = 0x01; /* * OPEN_SESSION payload: * byte (identifier length) * byte[] (identifier) * byte (address length) * byte[] (address) * short (port) * long (clientID) */ const PACKET_OPEN_SESSION = 0x02; /* * CLOSE_SESSION payload: * byte (identifier length) * byte[] (identifier) * string (reason) */ const PACKET_CLOSE_SESSION = 0x03; /* * INVALID_SESSION payload: * byte (identifier length) * byte[] (identifier) */ const PACKET_INVALID_SESSION = 0x04; /* TODO: implement this * SEND_QUEUE payload: * byte (identifier length) * byte[] (identifier) */ const PACKET_SEND_QUEUE = 0x05; /* * ACK_NOTIFICATION payload: * byte (identifier length) * byte[] (identifier) * int (identifierACK) */ const PACKET_ACK_NOTIFICATION = 0x06; /* * SET_OPTION payload: * byte (option name length) * byte[] (option name) * byte[] (option value) */ const PACKET_SET_OPTION = 0x07; /* * RAW payload: * byte (address length) * byte[] (address from/to) * short (port) * byte[] (payload) */ const PACKET_RAW = 0x08; /* * RAW payload: * byte (address length) * byte[] (address) * int (timeout) */ const PACKET_BLOCK_ADDRESS = 0x09; /* * RAW payload: * byte (address length) * byte[] (address) */ const PACKET_UNBLOCK_ADDRESS = 0x0a; /* * No payload * * Sends the disconnect message, removes sessions correctly, closes sockets. */ const PACKET_SHUTDOWN = 0x7e; /* * No payload * * Leaves everything as-is and halts, other Threads can be in a post-crash condition. */ const PACKET_EMERGENCY_SHUTDOWN = 0x7f; public static function bootstrap(\ClassLoader $loader){ $loader->addPath(dirname(__FILE__) . DIRECTORY_SEPARATOR . ".."); } } abstract class AcknowledgePacket extends Packet{ /** @var int[] */ public $packets = []; public function encode(){ parent::encode(); $payload = ""; sort($this->packets, SORT_NUMERIC); $count = count($this->packets); $records = 0; if($count > 0){ $pointer = 1; $start = $this->packets[0]; $last = $this->packets[0]; while($pointer < $count){ $current = $this->packets[$pointer++]; $diff = $current - $last; if($diff === 1){ $last = $current; }elseif($diff > 1){ //Forget about duplicated packets (bad queues?) if($start === $last){ $payload .= "\x01"; $payload .= Binary::writeLTriad($start); $start = $last = $current; }else{ $payload .= "\x00"; $payload .= Binary::writeLTriad($start); $payload .= Binary::writeLTriad($last); $start = $last = $current; } ++$records; } } if($start === $last){ $payload .= "\x01"; $payload .= Binary::writeLTriad($start); }else{ $payload .= "\x00"; $payload .= Binary::writeLTriad($start); $payload .= Binary::writeLTriad($last); } ++$records; } $this->putShort($records); $this->buffer .= $payload; } public function decode(){ parent::decode(); $count = $this->getShort(); $this->packets = []; $cnt = 0; for($i = 0; $i < $count and !$this->feof() and $cnt < 4096; ++$i){ if($this->getByte() === 0){ $start = $this->getLTriad(); $end = $this->getLTriad(); if(($end - $start) > 512){ $end = $start + 512; } for($c = $start; $c <= $end; ++$c){ $this->packets[$cnt++] = $c; } }else{ $this->packets[$cnt++] = $this->getLTriad(); } } } public function clean(){ $this->packets = []; return parent::clean(); } } class CLIENT_CONNECT_DataPacket extends Packet{ public static $ID = 0x09; public $clientID; public $sendPing; public $useSecurity = false; public function encode(){ parent::encode(); $this->putLong($this->clientID); $this->putLong($this->sendPing); $this->putByte($this->useSecurity ? 1 : 0); } public function decode(){ parent::decode(); $this->clientID = $this->getLong(); $this->sendPing = $this->getLong(); $this->useSecurity = $this->getByte() > 0; } } class CLIENT_HANDSHAKE_DataPacket extends Packet{ public static $ID = 0x13; public $address; public $port; public $systemAddresses = []; public $sendPing; public $sendPong; public function encode(){ } public function decode(){ parent::decode(); $this->getAddress($this->address, $this->port); for($i = 0; $i < 10; ++$i){ $this->getAddress($addr, $port, $version); $this->systemAddresses[$i] = [$addr, $port, $version]; } $this->sendPing = $this->getLong(); $this->sendPong = $this->getLong(); } } abstract class DataPacket extends Packet{ /** @var EncapsulatedPacket[] */ public $packets = []; public $seqNumber; public function encode(){ parent::encode(); $this->putLTriad($this->seqNumber); foreach($this->packets as $packet){ $this->put($packet instanceof EncapsulatedPacket ? $packet->toBinary() : (string) $packet); } } public function length(){ $length = 4; foreach($this->packets as $packet){ $length += $packet instanceof EncapsulatedPacket ? $packet->getTotalLength() : strlen($packet); } return $length; } public function decode(){ parent::decode(); $this->seqNumber = $this->getLTriad(); while(!$this->feof()){ $offset = 0; $data = substr($this->buffer, $this->offset); $packet = EncapsulatedPacket::fromBinary($data, false, $offset); $this->offset += $offset; if(strlen($packet->buffer) === 0){ break; } $this->packets[] = $packet; } } public function clean(){ $this->packets = []; $this->seqNumber = null; return parent::clean(); } } class EncapsulatedPacket{ public $reliability; public $hasSplit = false; public $length = 0; public $messageIndex = null; public $orderIndex = null; public $orderChannel = null; public $splitCount = null; public $splitID = null; public $splitIndex = null; public $buffer; public $needACK = false; public $identifierACK = null; /** * @param string $binary * @param bool $internal * @param int &$offset * * @return EncapsulatedPacket */ public static function fromBinary($binary, $internal = false, &$offset = null){ $packet = new EncapsulatedPacket(); $flags = ord($binary{0}); $packet->reliability = $reliability = ($flags & 0b11100000) >> 5; $packet->hasSplit = $hasSplit = ($flags & 0b00010000) > 0; if($internal){ $length = Binary::readInt(substr($binary, 1, 4)); $packet->identifierACK = Binary::readInt(substr($binary, 5, 4)); $offset = 9; }else{ $length = (int) ceil(Binary::readShort(substr($binary, 1, 2)) / 8); $offset = 3; $packet->identifierACK = null; } if($reliability > PacketReliability::UNRELIABLE){ if($reliability >= PacketReliability::RELIABLE and $reliability !== PacketReliability::UNRELIABLE_WITH_ACK_RECEIPT){ $packet->messageIndex = Binary::readLTriad(substr($binary, $offset, 3)); $offset += 3; } if($reliability <= PacketReliability::RELIABLE_SEQUENCED and $reliability !== PacketReliability::RELIABLE){ $packet->orderIndex = Binary::readLTriad(substr($binary, $offset, 3)); $offset += 3; $packet->orderChannel = ord($binary{$offset++}); } } if($hasSplit){ $packet->splitCount = Binary::readInt(substr($binary, $offset, 4)); $offset += 4; $packet->splitID = Binary::readShort(substr($binary, $offset, 2)); $offset += 2; $packet->splitIndex = Binary::readInt(substr($binary, $offset, 4)); $offset += 4; } $packet->buffer = substr($binary, $offset, $length); $offset += $length; return $packet; } public function getTotalLength(){ return 3 + strlen($this->buffer) + ($this->messageIndex !== null ? 3 : 0) + ($this->orderIndex !== null ? 4 : 0) + ($this->hasSplit ? 10 : 0); } /** * @param bool $internal * * @return string */ public function toBinary($internal = false){ return chr(($this->reliability << 5) | ($this->hasSplit ? 0b00010000 : 0)) . ($internal ? Binary::writeInt(strlen($this->buffer)) . Binary::writeInt($this->identifierACK) : Binary::writeShort(strlen($this->buffer) << 3)) . ($this->reliability > PacketReliability::UNRELIABLE ? (($this->reliability >= PacketReliability::RELIABLE and $this->reliability !== PacketReliability::UNRELIABLE_WITH_ACK_RECEIPT) ? Binary::writeLTriad($this->messageIndex) : "") . (($this->reliability <= PacketReliability::RELIABLE_SEQUENCED and $this->reliability !== PacketReliability::RELIABLE) ? Binary::writeLTriad($this->orderIndex) . chr($this->orderChannel) : "") : "" ) . ($this->hasSplit ? Binary::writeInt($this->splitCount) . Binary::writeShort($this->splitID) . Binary::writeInt($this->splitIndex) : "") . $this->buffer; } public function __toString(){ return $this->toBinary(); } } use raklib\RakLib; class OPEN_CONNECTION_REPLY_1 extends Packet{ public static $ID = 0x06; public $serverID; public $mtuSize; public function encode(){ parent::encode(); $this->put(RakLib::MAGIC); $this->putLong($this->serverID); $this->putByte(0); //Server security $this->putShort($this->mtuSize); } public function decode(){ parent::decode(); $this->offset += 16; //Magic $this->serverID = $this->getLong(); $this->getByte(); //security $this->mtuSize = $this->getShort(); } } use raklib\RakLib; class OPEN_CONNECTION_REPLY_2 extends Packet{ public static $ID = 0x08; public $serverID; public $clientAddress; public $clientPort; public $mtuSize; public function encode(){ parent::encode(); $this->put(RakLib::MAGIC); $this->putLong($this->serverID); $this->putAddress($this->clientAddress, $this->clientPort, 4); $this->putShort($this->mtuSize); $this->putByte(0); //server security } public function decode(){ parent::decode(); $this->offset += 16; //Magic $this->serverID = $this->getLong(); $this->getAddress($this->clientAddress, $this->clientPort); $this->mtuSize = $this->getShort(); //server security } } use raklib\RakLib; class OPEN_CONNECTION_REQUEST_1 extends Packet{ public static $ID = 0x05; public $protocol = RakLib::PROTOCOL; public $mtuSize; public function encode(){ parent::encode(); $this->put(RakLib::MAGIC); $this->putByte($this->protocol); $this->put(str_repeat(chr(0x00), $this->mtuSize - 18)); } public function decode(){ parent::decode(); $this->offset += 16; //Magic $this->protocol = $this->getByte(); $this->mtuSize = strlen($this->get(true)) + 18; } } use raklib\RakLib; class OPEN_CONNECTION_REQUEST_2 extends Packet{ public static $ID = 0x07; public $clientID; public $serverAddress; public $serverPort; public $mtuSize; public function encode(){ parent::encode(); $this->put(RakLib::MAGIC); $this->putAddress($this->serverAddress, $this->serverPort, 4); $this->putShort($this->mtuSize); $this->putLong($this->clientID); } public function decode(){ parent::decode(); $this->offset += 16; //Magic $this->getAddress($this->serverAddress, $this->serverPort); $this->mtuSize = $this->getShort(); $this->clientID = $this->getLong(); } } class PING_DataPacket extends Packet{ public static $ID = 0x00; public $pingID; public function encode(){ parent::encode(); $this->putLong($this->pingID); } public function decode(){ parent::decode(); $this->pingID = $this->getLong(); } } class PONG_DataPacket extends Packet{ public static $ID = 0x03; public $pingID; public function encode(){ parent::encode(); $this->putLong($this->pingID); } public function decode(){ parent::decode(); $this->pingID = $this->getLong(); } } abstract class Packet{ public static $ID = -1; protected $offset = 0; public $buffer; public $sendTime; protected function get($len){ if($len < 0){ $this->offset = strlen($this->buffer) - 1; return ""; }elseif($len === true){ return substr($this->buffer, $this->offset); } return $len === 1 ? $this->buffer{$this->offset++} : substr($this->buffer, ($this->offset += $len) - $len, $len); } protected function getLong($signed = true){ return Binary::readLong($this->get(8), $signed); } protected function getInt(){ return Binary::readInt($this->get(4)); } protected function getShort($signed = true){ return $signed ? Binary::readSignedShort($this->get(2)) : Binary::readShort($this->get(2)); } protected function getTriad(){ return Binary::readTriad($this->get(3)); } protected function getLTriad(){ return Binary::readLTriad($this->get(3)); } protected function getByte(){ return ord($this->buffer{$this->offset++}); } protected function getString(){ return $this->get($this->getShort()); } protected function getAddress(&$addr, &$port, &$version = null){ $version = $this->getByte(); if($version === 4){ $addr = ((~$this->getByte()) & 0xff) .".". ((~$this->getByte()) & 0xff) .".". ((~$this->getByte()) & 0xff) .".". ((~$this->getByte()) & 0xff); $port = $this->getShort(false); }else{ //TODO: IPv6 } } protected function feof(){ return !isset($this->buffer{$this->offset}); } protected function put($str){ $this->buffer .= $str; } protected function putLong($v){ $this->buffer .= Binary::writeLong($v); } protected function putInt($v){ $this->buffer .= Binary::writeInt($v); } protected function putShort($v){ $this->buffer .= Binary::writeShort($v); } protected function putTriad($v){ $this->buffer .= Binary::writeTriad($v); } protected function putLTriad($v){ $this->buffer .= Binary::writeLTriad($v); } protected function putByte($v){ $this->buffer .= chr($v); } protected function putString($v){ $this->putShort(strlen($v)); $this->put($v); } protected function putAddress($addr, $port, $version = 4){ $this->putByte($version); if($version === 4){ foreach(explode(".", $addr) as $b){ $this->putByte((~((int) $b)) & 0xff); } $this->putShort($port); }else{ //IPv6 } } public function encode(){ $this->buffer = chr(static::$ID); } public function decode(){ $this->offset = 1; } public function clean(){ $this->buffer = null; $this->offset = 0; $this->sendTime = null; return $this; } } class SERVER_HANDSHAKE_DataPacket extends Packet{ public static $ID = 0x10; public $address; public $port; public $systemAddresses = [ ["127.0.0.1", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4], ["0.0.0.0", 0, 4] ]; public $sendPing; public $sendPong; public function encode(){ parent::encode(); $this->putAddress($this->address, $this->port, 4); $this->putShort(0); for($i = 0; $i < 10; ++$i){ $this->putAddress($this->systemAddresses[$i][0], $this->systemAddresses[$i][1], $this->systemAddresses[$i][2]); } $this->putLong($this->sendPing); $this->putLong($this->sendPong); } public function decode(){ parent::decode(); //TODO, not needed yet } } use raklib\RakLib; class UNCONNECTED_PING extends Packet{ public static $ID = 0x01; public $pingID; public function encode(){ parent::encode(); $this->putLong($this->pingID); $this->put(RakLib::MAGIC); } public function decode(){ parent::decode(); $this->pingID = $this->getLong(); //magic } } class UNCONNECTED_PING_OPEN_CONNECTIONS extends UNCONNECTED_PING{ public static $ID = 0x02; } use raklib\RakLib; class UNCONNECTED_PONG extends Packet{ public static $ID = 0x1c; public $pingID; public $serverID; public $serverName; public function encode(){ parent::encode(); $this->putLong($this->pingID); $this->putLong($this->serverID); $this->put(RakLib::MAGIC); $this->putString($this->serverName); } public function decode(){ parent::decode(); $this->pingID = $this->getLong(); $this->serverID = $this->getLong(); $this->offset += 16; //magic $this->serverName = $this->getString(); } }port = (int) $port; if($port < 1 or $port > 65536){ throw new \Exception("Invalid port range"); } $this->interface = $interface; $this->logger = $logger; $this->loader = $loader; $loadPaths = []; $this->addDependency($loadPaths, new \ReflectionClass($logger)); $this->addDependency($loadPaths, new \ReflectionClass($loader)); $this->loadPaths = array_reverse($loadPaths); $this->shutdown = false; $this->externalQueue = new \Threaded; $this->internalQueue = new \Threaded; if(\Phar::running(true) !== ""){ $this->mainPath = \Phar::running(true); }else{ $this->mainPath = \getcwd() . DIRECTORY_SEPARATOR; } $this->start(); } protected function addDependency(array &$loadPaths, \ReflectionClass $dep){ if($dep->getFileName() !== false){ $loadPaths[$dep->getName()] = $dep->getFileName(); } if($dep->getParentClass() instanceof \ReflectionClass){ $this->addDependency($loadPaths, $dep->getParentClass()); } foreach($dep->getInterfaces() as $interface){ $this->addDependency($loadPaths, $interface); } } public function isShutdown(){ return $this->shutdown === true; } public function shutdown(){ $this->shutdown = true; } public function getPort(){ return $this->port; } public function getInterface(){ return $this->interface; } /** * @return \ThreadedLogger */ public function getLogger(){ return $this->logger; } /** * @return \Threaded */ public function getExternalQueue(){ return $this->externalQueue; } /** * @return \Threaded */ public function getInternalQueue(){ return $this->internalQueue; } public function pushMainToThreadPacket($str){ $this->internalQueue[] = $str; } public function readMainToThreadPacket(){ return $this->internalQueue->shift(); } public function pushThreadToMainPacket($str){ $this->externalQueue[] = $str; } public function readThreadToMainPacket(){ return $this->externalQueue->shift(); } public function shutdownHandler(){ if($this->shutdown !== true){ $this->getLogger()->emergency("RakLib crashed!"); } } public function errorHandler($errno, $errstr, $errfile, $errline, $context, $trace = null){ if(error_reporting() === 0){ return false; } $errorConversion = [ E_ERROR => "E_ERROR", E_WARNING => "E_WARNING", E_PARSE => "E_PARSE", E_NOTICE => "E_NOTICE", E_CORE_ERROR => "E_CORE_ERROR", E_CORE_WARNING => "E_CORE_WARNING", E_COMPILE_ERROR => "E_COMPILE_ERROR", E_COMPILE_WARNING => "E_COMPILE_WARNING", E_USER_ERROR => "E_USER_ERROR", E_USER_WARNING => "E_USER_WARNING", E_USER_NOTICE => "E_USER_NOTICE", E_STRICT => "E_STRICT", E_RECOVERABLE_ERROR => "E_RECOVERABLE_ERROR", E_DEPRECATED => "E_DEPRECATED", E_USER_DEPRECATED => "E_USER_DEPRECATED", ]; $errno = isset($errorConversion[$errno]) ? $errorConversion[$errno] : $errno; if(($pos = strpos($errstr, "\n")) !== false){ $errstr = substr($errstr, 0, $pos); } $errfile = $this->cleanPath($errfile); $this->getLogger()->debug("An $errno error happened: \"$errstr\" in \"$errfile\" at line $errline"); foreach(($trace = $this->getTrace($trace === null ? 3 : 0, $trace)) as $i => $line){ $this->getLogger()->debug($line); } return true; } public function getTrace($start = 1, $trace = null){ if($trace === null){ if(function_exists("xdebug_get_function_stack")){ $trace = array_reverse(xdebug_get_function_stack()); }else{ $e = new \Exception(); $trace = $e->getTrace(); } } $messages = []; $j = 0; for($i = (int) $start; isset($trace[$i]); ++$i, ++$j){ $params = ""; if(isset($trace[$i]["args"]) or isset($trace[$i]["params"])){ if(isset($trace[$i]["args"])){ $args = $trace[$i]["args"]; }else{ $args = $trace[$i]["params"]; } foreach($args as $name => $value){ $params .= (is_object($value) ? get_class($value) . " " . (method_exists($value, "__toString") ? $value->__toString() : "object") : gettype($value) . " " . @strval($value)) . ", "; } } $messages[] = "#$j " . (isset($trace[$i]["file"]) ? $this->cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" or $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . substr($params, 0, -2) . ")"; } return $messages; } public function cleanPath($path){ return rtrim(str_replace(["\\", ".php", "phar://", rtrim(str_replace(["\\", "phar://"], ["/", ""], $this->mainPath), "/")], ["/", "", "", ""], $path), "/"); } public function run(){ try{ //Load removed dependencies, can't use require_once() foreach($this->loadPaths as $name => $path){ if(!class_exists($name, false) and !interface_exists($name, false)){ require($path); } } $this->loader->register(true); gc_enable(); error_reporting(-1); ini_set("display_errors", 1); ini_set("display_startup_errors", 1); set_error_handler([$this, "errorHandler"], E_ALL); register_shutdown_function([$this, "shutdownHandler"]); $socket = new UDPServerSocket($this->getLogger(), $this->port, $this->interface); new SessionManager($this, $socket); }catch(\Throwable $e){ $this->logger->logException($e); } } } server = $server; $this->instance = $instance; } public function sendEncapsulated($identifier, EncapsulatedPacket $packet, $flags = RakLib::PRIORITY_NORMAL){ $buffer = chr(RakLib::PACKET_ENCAPSULATED) . chr(strlen($identifier)) . $identifier . chr($flags) . $packet->toBinary(true); $this->server->pushMainToThreadPacket($buffer); } public function sendRaw($address, $port, $payload){ $buffer = chr(RakLib::PACKET_RAW) . chr(strlen($address)) . $address . Binary::writeShort($port) . $payload; $this->server->pushMainToThreadPacket($buffer); } public function closeSession($identifier, $reason){ $buffer = chr(RakLib::PACKET_CLOSE_SESSION) . chr(strlen($identifier)) . $identifier . chr(strlen($reason)) . $reason; $this->server->pushMainToThreadPacket($buffer); } public function sendOption($name, $value){ $buffer = chr(RakLib::PACKET_SET_OPTION) . chr(strlen($name)) . $name . $value; $this->server->pushMainToThreadPacket($buffer); } public function blockAddress($address, $timeout){ $buffer = chr(RakLib::PACKET_BLOCK_ADDRESS) . chr(strlen($address)) . $address . Binary::writeInt($timeout); $this->server->pushMainToThreadPacket($buffer); } public function unblockAddress($address){ $buffer = chr(RakLib::PACKET_UNBLOCK_ADDRESS) . chr(strlen($address)) . $address; $this->server->pushMainToThreadPacket($buffer); } public function shutdown(){ $buffer = chr(RakLib::PACKET_SHUTDOWN); $this->server->pushMainToThreadPacket($buffer); $this->server->shutdown(); $this->server->synchronized(function(){ if($this->server !== null){ $this->server->wait(20000); } }); $this->server->join(); } public function emergencyShutdown(){ $this->server->shutdown(); $this->server->pushMainToThreadPacket("\x7f"); //RakLib::PACKET_EMERGENCY_SHUTDOWN } protected function invalidSession($identifier){ $buffer = chr(RakLib::PACKET_INVALID_SESSION) . chr(strlen($identifier)) . $identifier; $this->server->pushMainToThreadPacket($buffer); } /** * @return bool */ public function handlePacket(){ if(strlen($packet = $this->server->readThreadToMainPacket()) > 0){ $id = ord($packet{0}); $offset = 1; if($id === RakLib::PACKET_ENCAPSULATED){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); $offset += $len; $flags = ord($packet{$offset++}); $buffer = substr($packet, $offset); $this->instance->handleEncapsulated($identifier, EncapsulatedPacket::fromBinary($buffer, true), $flags); }elseif($id === RakLib::PACKET_RAW){ $len = ord($packet{$offset++}); $address = substr($packet, $offset, $len); $offset += $len; $port = Binary::readShort(substr($packet, $offset, 2)); $offset += 2; $payload = substr($packet, $offset); $this->instance->handleRaw($address, $port, $payload); }elseif($id === RakLib::PACKET_SET_OPTION){ $len = ord($packet{$offset++}); $name = substr($packet, $offset, $len); $offset += $len; $value = substr($packet, $offset); $this->instance->handleOption($name, $value); }elseif($id === RakLib::PACKET_OPEN_SESSION){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); $offset += $len; $len = ord($packet{$offset++}); $address = substr($packet, $offset, $len); $offset += $len; $port = Binary::readShort(substr($packet, $offset, 2)); $offset += 2; $clientID = Binary::readLong(substr($packet, $offset, 8)); $this->instance->openSession($identifier, $address, $port, $clientID); }elseif($id === RakLib::PACKET_CLOSE_SESSION){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); $offset += $len; $len = ord($packet{$offset++}); $reason = substr($packet, $offset, $len); $this->instance->closeSession($identifier, $reason); }elseif($id === RakLib::PACKET_INVALID_SESSION){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); $this->instance->closeSession($identifier, "Invalid session"); }elseif($id === RakLib::PACKET_ACK_NOTIFICATION){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); $offset += $len; $identifierACK = Binary::readInt(substr($packet, $offset, 4)); $this->instance->notifyACK($identifier, $identifierACK); } return true; } return false; } }sessionManager = $sessionManager; $this->address = $address; $this->port = $port; $this->sendQueue = new DATA_PACKET_4(); $this->lastUpdate = microtime(true); $this->startTime = microtime(true); $this->isActive = false; $this->windowStart = -1; $this->windowEnd = self::$WINDOW_SIZE; $this->reliableWindowStart = 0; $this->reliableWindowEnd = self::$WINDOW_SIZE; for($i = 0; $i < 32; ++$i){ $this->channelIndex[$i] = 0; } } public function getAddress(){ return $this->address; } public function getPort(){ return $this->port; } public function getID(){ return $this->id; } public function update($time){ if(!$this->isActive and ($this->lastUpdate + 10) < $time){ $this->disconnect("timeout"); return; } $this->isActive = false; if(count($this->ACKQueue) > 0){ $pk = new ACK(); $pk->packets = $this->ACKQueue; $this->sendPacket($pk); $this->ACKQueue = []; } if(count($this->NACKQueue) > 0){ $pk = new NACK(); $pk->packets = $this->NACKQueue; $this->sendPacket($pk); $this->NACKQueue = []; } if(count($this->packetToSend) > 0){ $limit = 16; foreach($this->packetToSend as $k => $pk){ $pk->sendTime = $time; $pk->encode(); $this->recoveryQueue[$pk->seqNumber] = $pk; unset($this->packetToSend[$k]); $this->sendPacket($pk); if(--$limit <= 0){ break; } } if(count($this->packetToSend) > self::$WINDOW_SIZE){ $this->packetToSend = []; } } if(count($this->needACK) > 0){ foreach($this->needACK as $identifierACK => $indexes){ if(count($indexes) === 0){ unset($this->needACK[$identifierACK]); $this->sessionManager->notifyACK($this, $identifierACK); } } } foreach($this->recoveryQueue as $seq => $pk){ if($pk->sendTime < (time() - 8)){ $this->packetToSend[] = $pk; unset($this->recoveryQueue[$seq]); }else{ break; } } foreach($this->receivedWindow as $seq => $bool){ if($seq < $this->windowStart){ unset($this->receivedWindow[$seq]); }else{ break; } } $this->sendQueue(); } public function disconnect($reason = "unknown"){ $this->sessionManager->removeSession($this, $reason); } private function sendPacket(Packet $packet){ $this->sessionManager->sendPacket($packet, $this->address, $this->port); } public function sendQueue(){ if(count($this->sendQueue->packets) > 0){ $this->sendQueue->seqNumber = $this->sendSeqNumber++; $this->sendPacket($this->sendQueue); $this->sendQueue->sendTime = microtime(true); $this->recoveryQueue[$this->sendQueue->seqNumber] = $this->sendQueue; $this->sendQueue = new DATA_PACKET_4(); } } /** * @param EncapsulatedPacket $pk * @param int $flags */ private function addToQueue(EncapsulatedPacket $pk, $flags = RakLib::PRIORITY_NORMAL){ $priority = $flags & 0b0000111; if($pk->needACK and $pk->messageIndex !== null){ $this->needACK[$pk->identifierACK][$pk->messageIndex] = $pk->messageIndex; } if($priority === RakLib::PRIORITY_IMMEDIATE){ //Skip queues $packet = new DATA_PACKET_0(); $packet->seqNumber = $this->sendSeqNumber++; if($pk->needACK){ $packet->packets[] = clone $pk; $pk->needACK = false; }else{ $packet->packets[] = $pk->toBinary(); } $this->sendPacket($packet); $packet->sendTime = microtime(true); $this->recoveryQueue[$packet->seqNumber] = $packet; return; } $length = $this->sendQueue->length(); if($length + $pk->getTotalLength() > $this->mtuSize){ $this->sendQueue(); } if($pk->needACK){ $this->sendQueue->packets[] = clone $pk; $pk->needACK = false; }else{ $this->sendQueue->packets[] = $pk->toBinary(); } } /** * @param EncapsulatedPacket $packet * @param int $flags */ public function addEncapsulatedToQueue(EncapsulatedPacket $packet, $flags = RakLib::PRIORITY_NORMAL){ if(($packet->needACK = ($flags & RakLib::FLAG_NEED_ACK) > 0) === true){ $this->needACK[$packet->identifierACK] = []; } if( $packet->reliability === PacketReliability::RELIABLE or $packet->reliability === PacketReliability::RELIABLE_ORDERED or $packet->reliability === PacketReliability::RELIABLE_SEQUENCED or $packet->reliability === PacketReliability::RELIABLE_WITH_ACK_RECEIPT or $packet->reliability === PacketReliability::RELIABLE_ORDERED_WITH_ACK_RECEIPT ){ $packet->messageIndex = $this->messageIndex++; if($packet->reliability === PacketReliability::RELIABLE_ORDERED){ $packet->orderIndex = $this->channelIndex[$packet->orderChannel]++; } } if($packet->getTotalLength() + 4 > $this->mtuSize){ $buffers = str_split($packet->buffer, $this->mtuSize - 34); $splitID = ++$this->splitID % 65536; foreach($buffers as $count => $buffer){ $pk = new EncapsulatedPacket(); $pk->splitID = $splitID; $pk->hasSplit = true; $pk->splitCount = count($buffers); $pk->reliability = $packet->reliability; $pk->splitIndex = $count; $pk->buffer = $buffer; if($count > 0){ $pk->messageIndex = $this->messageIndex++; }else{ $pk->messageIndex = $packet->messageIndex; } if($pk->reliability === PacketReliability::RELIABLE_ORDERED){ $pk->orderChannel = $packet->orderChannel; $pk->orderIndex = $packet->orderIndex; } $this->addToQueue($pk, $flags | RakLib::PRIORITY_IMMEDIATE); } }else{ $this->addToQueue($packet, $flags); } } private function handleSplit(EncapsulatedPacket $packet){ if($packet->splitCount >= self::MAX_SPLIT_SIZE or $packet->splitIndex >= self::MAX_SPLIT_SIZE or $packet->splitIndex < 0){ return; } if(!isset($this->splitPackets[$packet->splitID])){ if(count($this->splitPackets) >= self::MAX_SPLIT_COUNT){ return; } $this->splitPackets[$packet->splitID] = [$packet->splitIndex => $packet]; }else{ $this->splitPackets[$packet->splitID][$packet->splitIndex] = $packet; } if(count($this->splitPackets[$packet->splitID]) === $packet->splitCount){ $pk = new EncapsulatedPacket(); $pk->buffer = ""; for($i = 0; $i < $packet->splitCount; ++$i){ $pk->buffer .= $this->splitPackets[$packet->splitID][$i]->buffer; } $pk->length = strlen($pk->buffer); unset($this->splitPackets[$packet->splitID]); $this->handleEncapsulatedPacketRoute($pk); } } private function handleEncapsulatedPacket(EncapsulatedPacket $packet){ if($packet->messageIndex === null){ $this->handleEncapsulatedPacketRoute($packet); }else{ if($packet->messageIndex < $this->reliableWindowStart or $packet->messageIndex > $this->reliableWindowEnd){ return; } if(($packet->messageIndex - $this->lastReliableIndex) === 1){ $this->lastReliableIndex++; $this->reliableWindowStart++; $this->reliableWindowEnd++; $this->handleEncapsulatedPacketRoute($packet); if(count($this->reliableWindow) > 0){ ksort($this->reliableWindow); foreach($this->reliableWindow as $index => $pk){ if(($index - $this->lastReliableIndex) !== 1){ break; } $this->lastReliableIndex++; $this->reliableWindowStart++; $this->reliableWindowEnd++; $this->handleEncapsulatedPacketRoute($pk); unset($this->reliableWindow[$index]); } } }else{ $this->reliableWindow[$packet->messageIndex] = $packet; } } } public function getState(){ return $this->state; } public function isTemporal(){ return $this->isTemporal; } private function handleEncapsulatedPacketRoute(EncapsulatedPacket $packet){ if($this->sessionManager === null){ return; } if($packet->hasSplit){ if($this->state === self::STATE_CONNECTED){ $this->handleSplit($packet); } return; } $id = ord($packet->buffer{0}); if($id < 0x80){ //internal data packet if($this->state === self::STATE_CONNECTING_2){ if($id === CLIENT_CONNECT_DataPacket::$ID){ $dataPacket = new CLIENT_CONNECT_DataPacket; $dataPacket->buffer = $packet->buffer; $dataPacket->decode(); $pk = new SERVER_HANDSHAKE_DataPacket; $pk->address = $this->address; $pk->port = $this->port; $pk->sendPing = $dataPacket->sendPing; $pk->sendPong = bcadd($pk->sendPing, "1000"); $pk->encode(); $sendPacket = new EncapsulatedPacket(); $sendPacket->reliability = PacketReliability::UNRELIABLE; $sendPacket->buffer = $pk->buffer; $this->addToQueue($sendPacket, RakLib::PRIORITY_IMMEDIATE); }elseif($id === CLIENT_HANDSHAKE_DataPacket::$ID){ $dataPacket = new CLIENT_HANDSHAKE_DataPacket; $dataPacket->buffer = $packet->buffer; $dataPacket->decode(); if($dataPacket->port === $this->sessionManager->getPort() or !$this->sessionManager->portChecking){ $this->state = self::STATE_CONNECTED; //FINALLY! $this->isTemporal = false; $this->sessionManager->openSession($this); } } }elseif($id === CLIENT_DISCONNECT_DataPacket::$ID){ $this->disconnect("client disconnect"); }elseif($id === PING_DataPacket::$ID){ $dataPacket = new PING_DataPacket; $dataPacket->buffer = $packet->buffer; $dataPacket->decode(); $pk = new PONG_DataPacket; $pk->pingID = $dataPacket->pingID; $pk->encode(); $sendPacket = new EncapsulatedPacket(); $sendPacket->reliability = PacketReliability::UNRELIABLE; $sendPacket->buffer = $pk->buffer; $this->addToQueue($sendPacket); }//TODO: add PING/PONG (0x00/0x03) automatic latency measure }elseif($this->state === self::STATE_CONNECTED){ $this->sessionManager->streamEncapsulated($this, $packet); //TODO: stream channels }else{ //$this->sessionManager->getLogger()->notice("Received packet before connection: " . bin2hex($packet->buffer)); } } public function handlePacket(Packet $packet){ $this->isActive = true; $this->lastUpdate = microtime(true); if($this->state === self::STATE_CONNECTED or $this->state === self::STATE_CONNECTING_2){ if($packet::$ID >= 0x80 and $packet::$ID <= 0x8f and $packet instanceof DataPacket){ //Data packet $packet->decode(); if($packet->seqNumber < $this->windowStart or $packet->seqNumber > $this->windowEnd or isset($this->receivedWindow[$packet->seqNumber])){ return; } $diff = $packet->seqNumber - $this->lastSeqNumber; unset($this->NACKQueue[$packet->seqNumber]); $this->ACKQueue[$packet->seqNumber] = $packet->seqNumber; $this->receivedWindow[$packet->seqNumber] = $packet->seqNumber; if($diff !== 1){ for($i = $this->lastSeqNumber + 1; $i < $packet->seqNumber; ++$i){ if(!isset($this->receivedWindow[$i])){ $this->NACKQueue[$i] = $i; } } } if($diff >= 1){ $this->lastSeqNumber = $packet->seqNumber; $this->windowStart += $diff; $this->windowEnd += $diff; } foreach($packet->packets as $pk){ $this->handleEncapsulatedPacket($pk); } }else{ if($packet instanceof ACK){ $packet->decode(); foreach($packet->packets as $seq){ if(isset($this->recoveryQueue[$seq])){ foreach($this->recoveryQueue[$seq]->packets as $pk){ if($pk instanceof EncapsulatedPacket and $pk->needACK and $pk->messageIndex !== null){ unset($this->needACK[$pk->identifierACK][$pk->messageIndex]); } } unset($this->recoveryQueue[$seq]); } } }elseif($packet instanceof NACK){ $packet->decode(); foreach($packet->packets as $seq){ if(isset($this->recoveryQueue[$seq])){ $pk = $this->recoveryQueue[$seq]; $pk->seqNumber = $this->sendSeqNumber++; $this->packetToSend[] = $pk; unset($this->recoveryQueue[$seq]); } } } } }elseif($packet::$ID > 0x00 and $packet::$ID < 0x80){ //Not Data packet :) $packet->decode(); if($packet instanceof OPEN_CONNECTION_REQUEST_1){ $packet->protocol; //TODO: check protocol number and refuse connections $pk = new OPEN_CONNECTION_REPLY_1(); $pk->mtuSize = $packet->mtuSize; $pk->serverID = $this->sessionManager->getID(); $this->sendPacket($pk); $this->state = self::STATE_CONNECTING_1; }elseif($this->state === self::STATE_CONNECTING_1 and $packet instanceof OPEN_CONNECTION_REQUEST_2){ $this->id = $packet->clientID; if($packet->serverPort === $this->sessionManager->getPort() or !$this->sessionManager->portChecking){ $this->mtuSize = min(abs($packet->mtuSize), 1464); //Max size, do not allow creating large buffers to fill server memory $pk = new OPEN_CONNECTION_REPLY_2(); $pk->mtuSize = $this->mtuSize; $pk->serverID = $this->sessionManager->getID(); $pk->clientAddress = $this->address; $pk->clientPort = $this->port; $this->sendPacket($pk); $this->state = self::STATE_CONNECTING_2; } } } } public function close(){ $data = "\x60\x00\x08\x00\x00\x00\x00\x00\x00\x00\x15"; $this->addEncapsulatedToQueue(EncapsulatedPacket::fromBinary($data)); //CLIENT_DISCONNECT packet 0x15 $this->sessionManager = null; } } server = $server; $this->socket = $socket; $this->registerPackets(); $this->serverId = mt_rand(0, PHP_INT_MAX); $this->run(); } public function getPort(){ return $this->server->getPort(); } public function getLogger(){ return $this->server->getLogger(); } public function run(){ $this->tickProcessor(); } private function tickProcessor(){ $this->lastMeasure = microtime(true); while(!$this->shutdown){ $start = microtime(true); $max = 5000; while(--$max and $this->receivePacket()); while($this->receiveStream()); $time = microtime(true) - $start; if($time < 0.05){ time_sleep_until(microtime(true) + 0.05 - $time); } $this->tick(); } } private function tick(){ $time = microtime(true); foreach($this->sessions as $session){ $session->update($time); } foreach($this->ipSec as $address => $count){ if($count >= $this->packetLimit){ $this->blockAddress($address); } } $this->ipSec = []; if(($this->ticks & 0b1111) === 0){ $diff = max(0.005, $time - $this->lastMeasure); $this->streamOption("bandwidth", serialize([ "up" => $this->sendBytes / $diff, "down" => $this->receiveBytes / $diff ])); $this->lastMeasure = $time; $this->sendBytes = 0; $this->receiveBytes = 0; if(count($this->block) > 0){ asort($this->block); $now = microtime(true); foreach($this->block as $address => $timeout){ if($timeout <= $now){ unset($this->block[$address]); }else{ break; } } } } ++$this->ticks; } private function receivePacket(){ $len = $this->socket->readPacket($buffer, $source, $port); if($buffer !== null){ $this->receiveBytes += $len; if(isset($this->block[$source])){ return true; } if(isset($this->ipSec[$source])){ $this->ipSec[$source]++; }else{ $this->ipSec[$source] = 1; } if($len > 0){ $pid = ord($buffer{0}); if($pid === UNCONNECTED_PING::$ID){ //No need to create a session for just pings $packet = new UNCONNECTED_PING; $packet->buffer = $buffer; $packet->decode(); $pk = new UNCONNECTED_PONG(); $pk->serverID = $this->getID(); $pk->pingID = $packet->pingID; $pk->serverName = $this->getName(); $this->sendPacket($pk, $source, $port); }elseif($pid === UNCONNECTED_PONG::$ID){ //ignored }elseif(($packet = $this->getPacketFromPool($pid)) !== null){ $packet->buffer = $buffer; $this->getSession($source, $port)->handlePacket($packet); }else{ $this->streamRaw($source, $port, $buffer); } } return true; } return false; } public function sendPacket(Packet $packet, $dest, $port){ $packet->encode(); $this->sendBytes += $this->socket->writePacket($packet->buffer, $dest, $port); } public function streamEncapsulated(Session $session, EncapsulatedPacket $packet, $flags = RakLib::PRIORITY_NORMAL){ $id = $session->getAddress() . ":" . $session->getPort(); $buffer = chr(RakLib::PACKET_ENCAPSULATED) . chr(strlen($id)) . $id . chr($flags) . $packet->toBinary(true); $this->server->pushThreadToMainPacket($buffer); } public function streamRaw($address, $port, $payload){ $buffer = chr(RakLib::PACKET_RAW) . chr(strlen($address)) . $address . Binary::writeShort($port) . $payload; $this->server->pushThreadToMainPacket($buffer); } protected function streamClose($identifier, $reason){ $buffer = chr(RakLib::PACKET_CLOSE_SESSION) . chr(strlen($identifier)) . $identifier . chr(strlen($reason)) . $reason; $this->server->pushThreadToMainPacket($buffer); } protected function streamInvalid($identifier){ $buffer = chr(RakLib::PACKET_INVALID_SESSION) . chr(strlen($identifier)) . $identifier; $this->server->pushThreadToMainPacket($buffer); } protected function streamOpen(Session $session){ $identifier = $session->getAddress() . ":" . $session->getPort(); $buffer = chr(RakLib::PACKET_OPEN_SESSION) . chr(strlen($identifier)) . $identifier . chr(strlen($session->getAddress())) . $session->getAddress() . Binary::writeShort($session->getPort()) . Binary::writeLong($session->getID()); $this->server->pushThreadToMainPacket($buffer); } protected function streamACK($identifier, $identifierACK){ $buffer = chr(RakLib::PACKET_ACK_NOTIFICATION) . chr(strlen($identifier)) . $identifier . Binary::writeInt($identifierACK); $this->server->pushThreadToMainPacket($buffer); } protected function streamOption($name, $value){ $buffer = chr(RakLib::PACKET_SET_OPTION) . chr(strlen($name)) . $name . $value; $this->server->pushThreadToMainPacket($buffer); } private function checkSessions(){ if(count($this->sessions) > 4096){ foreach($this->sessions as $i => $s){ if($s->isTemporal()){ unset($this->sessions[$i]); if(count($this->sessions) <= 4096){ break; } } } } } public function receiveStream(){ if(strlen($packet = $this->server->readMainToThreadPacket()) > 0){ $id = ord($packet{0}); $offset = 1; if($id === RakLib::PACKET_ENCAPSULATED){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); $offset += $len; if(isset($this->sessions[$identifier])){ $flags = ord($packet{$offset++}); $buffer = substr($packet, $offset); $this->sessions[$identifier]->addEncapsulatedToQueue(EncapsulatedPacket::fromBinary($buffer, true), $flags); }else{ $this->streamInvalid($identifier); } }elseif($id === RakLib::PACKET_RAW){ $len = ord($packet{$offset++}); $address = substr($packet, $offset, $len); $offset += $len; $port = Binary::readShort(substr($packet, $offset, 2)); $offset += 2; $payload = substr($packet, $offset); $this->socket->writePacket($payload, $address, $port); }elseif($id === RakLib::PACKET_CLOSE_SESSION){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); if(isset($this->sessions[$identifier])){ $this->removeSession($this->sessions[$identifier]); }else{ $this->streamInvalid($identifier); } }elseif($id === RakLib::PACKET_INVALID_SESSION){ $len = ord($packet{$offset++}); $identifier = substr($packet, $offset, $len); if(isset($this->sessions[$identifier])){ $this->removeSession($this->sessions[$identifier]); } }elseif($id === RakLib::PACKET_SET_OPTION){ $len = ord($packet{$offset++}); $name = substr($packet, $offset, $len); $offset += $len; $value = substr($packet, $offset); switch($name){ case "name": $this->name = $value; break; case "portChecking": $this->portChecking = (bool) $value; break; case "packetLimit": $this->packetLimit = (int) $value; break; } }elseif($id === RakLib::PACKET_BLOCK_ADDRESS){ $len = ord($packet{$offset++}); $address = substr($packet, $offset, $len); $offset += $len; $timeout = Binary::readInt(substr($packet, $offset, 4)); $this->blockAddress($address, $timeout); }elseif($id === RakLib::PACKET_UNBLOCK_ADDRESS){ $len = ord($packet{$offset++}); $address = substr($packet, $offset, $len); $offset += $len; $this->unblockAddress($address); }elseif($id === RakLib::PACKET_SHUTDOWN){ foreach($this->sessions as $session){ $this->removeSession($session); } $this->socket->close(); $this->shutdown = true; }elseif($id === RakLib::PACKET_EMERGENCY_SHUTDOWN){ $this->shutdown = true; }else{ return false; } return true; } return false; } public function blockAddress($address, $timeout = 300){ $final = microtime(true) + $timeout; if(!isset($this->block[$address]) or $timeout === -1){ if($timeout === -1){ $final = PHP_INT_MAX; }else{ $this->getLogger()->notice("Blocked $address for $timeout seconds"); } $this->block[$address] = $final; }elseif($this->block[$address] < $final){ $this->block[$address] = $final; } } public function unblockAddress($address){ unset($this->block[$address]); } /** * @param string $ip * @param int $port * * @return Session */ public function getSession($ip, $port){ $id = $ip . ":" . $port; if(!isset($this->sessions[$id])){ $this->checkSessions(); $this->sessions[$id] = new Session($this, $ip, $port); } return $this->sessions[$id]; } public function removeSession(Session $session, $reason = "unknown"){ $id = $session->getAddress() . ":" . $session->getPort(); if(isset($this->sessions[$id])){ $this->sessions[$id]->close(); unset($this->sessions[$id]); $this->streamClose($id, $reason); } } public function openSession(Session $session){ $this->streamOpen($session); } public function notifyACK(Session $session, $identifierACK){ $this->streamACK($session->getAddress() . ":" . $session->getPort(), $identifierACK); } public function getName(){ return $this->name; } public function getID(){ return $this->serverId; } private function registerPacket($id, $class){ $this->packetPool[$id] = new $class; } /** * @param $id * * @return Packet */ public function getPacketFromPool($id){ if(isset($this->packetPool[$id])){ return clone $this->packetPool[$id]; } return null; } private function registerPackets(){ //$this->registerPacket(UNCONNECTED_PING::$ID, UNCONNECTED_PING::class); $this->registerPacket(UNCONNECTED_PING_OPEN_CONNECTIONS::$ID, UNCONNECTED_PING_OPEN_CONNECTIONS::class); $this->registerPacket(OPEN_CONNECTION_REQUEST_1::$ID, OPEN_CONNECTION_REQUEST_1::class); $this->registerPacket(OPEN_CONNECTION_REPLY_1::$ID, OPEN_CONNECTION_REPLY_1::class); $this->registerPacket(OPEN_CONNECTION_REQUEST_2::$ID, OPEN_CONNECTION_REQUEST_2::class); $this->registerPacket(OPEN_CONNECTION_REPLY_2::$ID, OPEN_CONNECTION_REPLY_2::class); $this->registerPacket(UNCONNECTED_PONG::$ID, UNCONNECTED_PONG::class); $this->registerPacket(ADVERTISE_SYSTEM::$ID, ADVERTISE_SYSTEM::class); $this->registerPacket(DATA_PACKET_0::$ID, DATA_PACKET_0::class); $this->registerPacket(DATA_PACKET_1::$ID, DATA_PACKET_1::class); $this->registerPacket(DATA_PACKET_2::$ID, DATA_PACKET_2::class); $this->registerPacket(DATA_PACKET_3::$ID, DATA_PACKET_3::class); $this->registerPacket(DATA_PACKET_4::$ID, DATA_PACKET_4::class); $this->registerPacket(DATA_PACKET_5::$ID, DATA_PACKET_5::class); $this->registerPacket(DATA_PACKET_6::$ID, DATA_PACKET_6::class); $this->registerPacket(DATA_PACKET_7::$ID, DATA_PACKET_7::class); $this->registerPacket(DATA_PACKET_8::$ID, DATA_PACKET_8::class); $this->registerPacket(DATA_PACKET_9::$ID, DATA_PACKET_9::class); $this->registerPacket(DATA_PACKET_A::$ID, DATA_PACKET_A::class); $this->registerPacket(DATA_PACKET_B::$ID, DATA_PACKET_B::class); $this->registerPacket(DATA_PACKET_C::$ID, DATA_PACKET_C::class); $this->registerPacket(DATA_PACKET_D::$ID, DATA_PACKET_D::class); $this->registerPacket(DATA_PACKET_E::$ID, DATA_PACKET_E::class); $this->registerPacket(DATA_PACKET_F::$ID, DATA_PACKET_F::class); $this->registerPacket(NACK::$ID, NACK::class); $this->registerPacket(ACK::$ID, ACK::class); } } socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); //socket_set_option($this->socket, SOL_SOCKET, SO_BROADCAST, 1); //Allow sending broadcast messages if(@socket_bind($this->socket, $interface, $port) === true){ socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 0); $this->setSendBuffer(1024 * 1024 * 8)->setRecvBuffer(1024 * 1024 * 8); }else{ $logger->critical("**** FAILED TO BIND TO " . $interface . ":" . $port . "!"); $logger->critical("Perhaps a server is already running on that port?"); exit(1); } socket_set_nonblock($this->socket); } public function getSocket(){ return $this->socket; } public function close(){ socket_close($this->socket); } /** * @param string &$buffer * @param string &$source * @param int &$port * * @return int */ public function readPacket(&$buffer, &$source, &$port){ return socket_recvfrom($this->socket, $buffer, 65535, 0, $source, $port); } /** * @param string $buffer * @param string $dest * @param int $port * * @return int */ public function writePacket($buffer, $dest, $port){ return socket_sendto($this->socket, $buffer, strlen($buffer), 0, $dest, $port); } /** * @param int $size * * @return $this */ public function setSendBuffer($size){ @socket_set_option($this->socket, SOL_SOCKET, SO_SNDBUF, $size); return $this; } /** * @param int $size * * @return $this */ public function setRecvBuffer($size){ @socket_set_option($this->socket, SOL_SOCKET, SO_RCVBUF, $size); return $this; } } ?> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class ArrayOutOfBoundsException extends OutOfBoundsException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ interface AttachableLogger extends \Logger{ /** * @param LoggerAttachment $attachment */ public function addAttachment(\LoggerAttachment $attachment); /** * @param LoggerAttachment $attachment */ public function removeAttachment(\LoggerAttachment $attachment); public function removeAttachments(); /** * @return \LoggerAttachment[] */ public function getAttachments(); } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ abstract class AttachableThreadedLogger extends \ThreadedLogger{ /** @var \ThreadedLoggerAttachment */ protected $attachment = null; /** * @param ThreadedLoggerAttachment $attachment */ public function addAttachment(\ThreadedLoggerAttachment $attachment){ if($this->attachment instanceof \ThreadedLoggerAttachment){ $this->attachment->addAttachment($attachment); }else{ $this->attachment = $attachment; } } /** * @param ThreadedLoggerAttachment $attachment */ public function removeAttachment(\ThreadedLoggerAttachment $attachment){ if($this->attachment instanceof \ThreadedLoggerAttachment){ if($this->attachment === $attachment){ $this->attachment = null; foreach($attachment->getAttachments() as $attachment){ $this->addAttachment($attachment); } } } } public function removeAttachments(){ if($this->attachment instanceof \ThreadedLoggerAttachment){ $this->attachment->removeAttachments(); $this->attachment = null; } } /** * @return \ThreadedLoggerAttachment[] */ public function getAttachments(){ $attachments = []; if($this->attachment instanceof \ThreadedLoggerAttachment){ $attachments[] = $this->attachment; $attachments += $this->attachment->getAttachments(); } return $attachments; } } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class BaseClassLoader extends \Threaded implements ClassLoader{ /** @var \ClassLoader */ private $parent; /** @var string[] */ private $lookup; /** @var string[] */ private $classes; /** * @param ClassLoader $parent */ public function __construct(ClassLoader $parent = null){ $this->parent = $parent; $this->lookup = new \Threaded; $this->classes = new \Threaded; } /** * Adds a path to the lookup list * * @param string $path * @param bool $prepend */ public function addPath($path, $prepend = false){ foreach($this->lookup as $p){ if($p === $path){ return; } } if($prepend){ $this->synchronized(function($path){ $entries = $this->getAndRemoveLookupEntries(); $this->lookup[] = $path; foreach($entries as $entry){ $this->lookup[] = $entry; } }, $path); }else{ $this->lookup[] = $path; } } protected function getAndRemoveLookupEntries(){ $entries = []; while($this->count() > 0){ $entries[] = $this->shift(); } return $entries; } /** * Removes a path from the lookup list * * @param $path */ public function removePath($path){ foreach($this->lookup as $i => $p){ if($p === $path){ unset($this->lookup[$i]); } } } /** * Returns an array of the classes loaded * * @return string[] */ public function getClasses(){ $classes = []; foreach($this->classes as $class){ $classes[] = $class; } return $classes; } /** * Returns the parent ClassLoader, if any * * @return ClassLoader */ public function getParent(){ return $this->parent; } /** * Attaches the ClassLoader to the PHP runtime * * @param bool $prepend * * @return bool */ public function register($prepend = false){ spl_autoload_register([$this, "loadClass"], true, $prepend); } /** * Called when there is a class to load * * @param string $name * * @return bool */ public function loadClass($name){ $path = $this->findClass($name); if($path !== null){ include($path); if(!class_exists($name, false) and !interface_exists($name, false) and !trait_exists($name, false)){ if($this->getParent() === null){ throw new ClassNotFoundException("Class $name not found"); } return false; } if(method_exists($name, "onClassLoaded") and (new ReflectionClass($name))->getMethod("onClassLoaded")->isStatic()){ $name::onClassLoaded(); } $this->classes[] = $name; return true; }elseif($this->getParent() === null){ throw new ClassNotFoundException("Class $name not found"); } return false; } /** * Returns the path for the class, if any * * @param string $name * * @return string|null */ public function findClass($name){ $components = explode("\\", $name); $baseName = implode(DIRECTORY_SEPARATOR, $components); foreach($this->lookup as $path){ if(PHP_INT_SIZE === 8 and file_exists($path . DIRECTORY_SEPARATOR . $baseName . "__64bit.php")){ return $path . DIRECTORY_SEPARATOR . $baseName . "__64bit.php"; }elseif(PHP_INT_SIZE === 4 and file_exists($path . DIRECTORY_SEPARATOR . $baseName . "__32bit.php")){ return $path . DIRECTORY_SEPARATOR . $baseName . "__32bit.php"; }elseif(file_exists($path . DIRECTORY_SEPARATOR . $baseName . ".php")){ return $path . DIRECTORY_SEPARATOR . $baseName . ".php"; } } return null; } } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class ClassCastException extends InvalidArgumentException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ interface ClassLoader{ /** * @param ClassLoader $parent */ public function __construct(ClassLoader $parent = null); /** * Adds a path to the lookup list * * @param string $path * @param bool $prepend */ public function addPath($path, $prepend = false); /** * Removes a path from the lookup list * * @param $path */ public function removePath($path); /** * Returns an array of the classes loaded * * @return string[] */ public function getClasses(); /** * Returns the parent ClassLoader, if any * * @return ClassLoader */ public function getParent(); /** * Attaches the ClassLoader to the PHP runtime * * @param bool $prepend * * @return bool */ public function register($prepend = false); /** * Called when there is a class to load * * @param string $name * * @return bool * * @throws ClassNotFoundException */ public function loadClass($name); /** * Returns the path for the class, if any * * @param string $name * * @return string|null */ public function findClass($name); } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class ClassNotFoundException extends LogicException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class InvalidArgumentCountException extends InvalidArgumentException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class InvalidKeyException extends InvalidArgumentException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class InvalidStateException extends InvalidArgumentException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ interface LogLevel{ const EMERGENCY = "emergency"; const ALERT = "alert"; const CRITICAL = "critical"; const ERROR = "error"; const WARNING = "warning"; const NOTICE = "notice"; const INFO = "info"; const DEBUG = "debug"; } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ interface Logger{ /** * System is unusable * * @param string $message */ public function emergency($message); /** * Action must me taken immediately * * @param string $message */ public function alert($message); /** * Critical conditions * * @param string $message */ public function critical($message); /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message */ public function error($message); /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message */ public function warning($message); /** * Normal but significant events. * * @param string $message */ public function notice($message); /** * Inersting events. * * @param string $message */ public function info($message); /** * Detailed debug information. * * @param string $message */ public function debug($message); /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message */ public function log($level, $message); /** * Logs a Throwable object * * @param Throwable $e * @param $trace */ public function logException(\Throwable $e, $trace = null); } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ interface LoggerAttachment{ /** * @param mixed $level * @param string $message */ public function log($level, $message); } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class SplFixedByteArray extends SplFixedArray{ private $convert; public function __construct($size, $convert = false){ parent::__construct($size); $this->convert = (bool) $convert; } public function chunk($start, $size, $normalize = true){ $end = $start + $size; if($normalize and $this->convert){ $d = ""; for($i = $start; $i < $end; ++$i){ $d .= chr($this[$i]); } }else{ $d = []; for($i = $start; $i < $end; ++$i){ $d[] = $this[$i]; } } return $d; } /** * @param string $str * @param bool $convert * * @return SplFixedByteArray */ public static function fromString($str, $convert = false){ $len = strlen($str); $ob = new SplFixedByteArray($len, $convert); if($convert){ for($i = 0; $i < $len; ++$i){ $ob[$i] = ord($str{$i}); } }else{ for($i = 0; $i < $len; ++$i){ $ob[$i] = $str{$i}; } } return $ob; } /** * @param string $str * @param int $size * @param int $start * @param bool $convert * * @return SplFixedByteArray */ public static function fromStringChunk($str, $size, $start = 0, $convert = false){ $ob = new SplFixedByteArray($size, $convert); if($convert){ for($i = 0; $i < $size; ++$i){ $ob[$i] = ord($str{$i + $start}); } }else{ for($i = 0; $i < $size; ++$i){ $ob[$i] = $str{$i + $start}; } } return $ob; } public function toString(){ $result = ""; if($this->convert){ for($i = 0; $i < $this->getSize(); ++$i){ $result .= chr($this[$i]); } }else{ for($i = 0; $i < $this->getSize(); ++$i){ $result .= $this[$i]; } } return $result; } public function __toString(){ return $this->toString(); } } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class StringOutOfBoundsException extends OutOfBoundsException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class ThreadException extends RuntimeException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ abstract class ThreadedLogger extends \Thread implements Logger{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ abstract class ThreadedLoggerAttachment extends \Threaded implements \LoggerAttachment{ /** @var \ThreadedLoggerAttachment */ protected $attachment = null; /** * @param mixed $level * @param string $message */ public final function call($level, $message){ $this->log($level, $message); if($this->attachment instanceof \ThreadedLoggerAttachment){ $this->attachment->call($level, $message); } } /** * @param ThreadedLoggerAttachment $attachment */ public function addAttachment(\ThreadedLoggerAttachment $attachment){ if($this->attachment instanceof \ThreadedLoggerAttachment){ $this->attachment->addAttachment($attachment); }else{ $this->attachment = $attachment; } } /** * @param ThreadedLoggerAttachment $attachment */ public function removeAttachment(\ThreadedLoggerAttachment $attachment){ if($this->attachment instanceof \ThreadedLoggerAttachment){ if($this->attachment === $attachment){ $this->attachment = null; foreach($attachment->getAttachments() as $attachment){ $this->addAttachment($attachment); } } } } public function removeAttachments(){ if($this->attachment instanceof \ThreadedLoggerAttachment){ $this->attachment->removeAttachments(); $this->attachment = null; } } /** * @return \ThreadedLoggerAttachment[] */ public function getAttachments(){ $attachments = []; if($this->attachment instanceof \ThreadedLoggerAttachment){ $attachments[] = $this->attachment; $attachments += $this->attachment->getAttachments(); } return $attachments; } } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class UndefinedConstantException extends InvalidStateException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class UndefinedPropertyException extends LogicException{ } * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ class UndefinedVariableException extends InvalidStateException{ } true, // if the specified database does not exist will create a new one 'error_if_exists' => false, // if the opened database exists will throw exception 'paranoid_checks' => false, 'block_cache_size' => 8 * (2 << 20), 'write_buffer_size' => 4<<20, 'block_size' => 4096, 'max_open_files' => 1000, 'block_restart_interval' => 16, 'compression' => LEVELDB_SNAPPY_COMPRESSION, 'comparator' => NULL, // any callable parameter return 0, -1, 1 ], array $read_options = [ 'verify_check_sum' => false, //may be set to true to force checksum verification of all data that is read from the file system on behalf of a particular read. By default, no such verification is done. 'fill_cache' => true, //When performing a bulk read, the application may set this to false to disable the caching so that the data processed by the bulk read does not end up displacing most of the cached contents. ], array $write_options = [ //Only one element named sync in the write option array. By default, each write to leveldb is asynchronous. 'sync' => false ]){} /** * @param string $key * @param array $read_options * * @return string|bool */ public function get($key, array $read_options = []){} /** * Alias of LevelDB::put() * * @param string $key * @param string $value * @param array $write_options */ public function set($key, $value, array $write_options = []){} /** * @param string $key * @param string $value * @param array $write_options */ public function put($key, $value, array $write_options = []){} /** * @param string $key * @param array $write_options * * @return bool */ public function delete($key, array $write_options = []){} /** * Executes all of the operations added in the write batch. * * @param LevelDBWriteBatch $batch * @param array $write_options */ public function write(LevelDBWriteBatch $batch, array $write_options = []){} /** * Valid properties: * - leveldb.stats: returns the status of the entire db * - leveldb.num-files-at-level: returns the number of files for each level. For example, you can use leveldb.num-files-at-level0 the number of files for zero level. * - leveldb.sstables: returns current status of sstables * * @param string $name * * @return mixed */ public function getProperty($name){} public function getApproximateSizes($start, $limit){} public function compactRange($start, $limit){} public function close(){} /** * @param array $options * * @return LevelDBIterator */ public function getIterator(array $options = []){} /** * @return LevelDBSnapshot */ public function getSnapshot(){} static public function destroy($name, array $options = []){} static public function repair($name, array $options = []){} } class LevelDBIterator implements Iterator{ public function __construct(LevelDB $db, array $read_options = []){} public function valid(){} public function rewind(){} public function last(){} public function seek($key){} public function next(){} public function prev(){} public function key(){} public function current(){} public function getError(){} public function destroy(){} } class LevelDBWriteBatch{ public function __construct($name, array $options = [], array $read_options = [], array $write_options = []){} public function set($key, $value, array $write_options = []){} public function put($key, $value, array $write_options = []){} public function delete($key, array $write_options = []){} public function clear(){} } class LevelDBSnapshot{ public function __construct(LevelDB $db){} public function release(){} } class LevelDBException extends Exception{ } , updated by Shoghi Cervantes * @version 3.0.0 * @link https://github.com/krakjoe/pthreads/blob/master/examples/stub.php */ /** * The default inheritance mask used when starting Threads and Workers */ define('PTHREADS_INHERIT_ALL', 0x111111); /** * Nothing will be inherited by the new context */ define('PTHREADS_INHERIT_NONE', 0); /** * Determines whether the ini entries are inherited by the new context */ define('PTHREADS_INHERIT_INI', 0x1); /** * Determines whether the constants are inherited by the new context */ define('PTHREADS_INHERIT_CONSTANTS', 0x10); /** * Determines whether the class table is inherited by the new context */ define('PTHREADS_INHERIT_CLASSES', 0x100); /** * Determines whether the function table is inherited by the new context */ define('PTHREADS_INHERIT_FUNCTIONS', 0x100); /** * Determines whether the included_files table is inherited by the new context */ define('PTHREADS_INHERIT_INCLUDES', 0x10000); /** * Determines whether the comments are inherited by the new context */ define('PTHREADS_INHERIT_COMMENTS', 0x100000); /** * Allow output headers from the threads */ define('PTHREADS_ALLOW_HEADERS', 0x1000000); /** * Allow global inheritance for new threads */ define('PTHREADS_ALLOW_GLOBALS', 0x10000000); interface Collectable{ /** * @return bool */ public function isGarbage(); } class Volatile extends Threaded{ } /** * Threaded class * * Threaded objects form the basis of pthreads ability to execute user code asynchronously; * they expose and include synchronization methods and various useful interfaces. * * Threaded objects, most importantly, provide implicit safety for the programmer; * all operations on the object scope are safe. * * @link http://www.php.net/manual/en/class.threaded.php * @since 2.0.0 */ class Threaded implements Traversable, Collectable{ /** * @param object $obj */ public static function extend($obj){ } /** * Fetches a chunk of the objects properties table of the given size * * @param int $size The number of items to fetch * @param bool $preserve default false * @return array An array of items from the objects member table * @link http://www.php.net/manual/en/threaded.chunk.php */ public function chunk($size, bool $preserve = false){} /** * {@inheritdoc} */ public function count(){} /** * Tell if the referenced object is executing * * @link http://www.php.net/manual/en/threaded.isrunning.php * @return bool A boolean indication of state */ public function isRunning(){} /** * Tell if the referenced object exited, suffered fatal errors, or threw uncaught exceptions during execution * * @link http://www.php.net/manual/en/threaded.isterminated.php * @return bool A boolean indication of state */ public function isTerminated(){} /** * Merges data into the current object * * @param mixed $from The data to merge * @param bool $overwrite Overwrite existing keys flag, by default true * * @link http://www.php.net/manual/en/threaded.merge.php * @return bool A boolean indication of success */ public function merge($from, $overwrite = true){} /** * Send notification to the referenced object * * @link http://www.php.net/manual/en/threaded.notify.php * @return bool A boolean indication of success */ public function notify(){} public function notifyOne(){} /** * {@inheritdoc} */ public function offsetGet($offset){} /** * {@inheritdoc} */ public function offsetSet($offset, $value){} /** * {@inheritdoc} */ public function offsetExists($offset){} /** * {@inheritdoc} */ public function offsetUnset($offset){} /** * Pops an item from the objects property table * * @link http://www.php.net/manual/en/threaded.pop.php * @return mixed The last item from the objects properties table */ public function pop(){} /** * The programmer should always implement the run method for objects that are intended for execution. * * @link http://www.php.net/manual/en/threaded.run.php * @return void The methods return value, if used, will be ignored */ public function run(){} /** * Shifts an item from the objects properties table * * @link http://www.php.net/manual/en/threaded.shift.php * @return mixed The first item from the objects properties table */ public function shift(){} /** * Executes the block while retaining the synchronization lock for the current context. * * @param \Closure $function The block of code to execute * @param mixed $args ... Variable length list of arguments to use as function arguments to the block * * @link http://www.php.net/manual/en/threaded.synchronized.php * @return mixed The return value from the block */ public function synchronized(\Closure $function, $args = null){} /** * Waits for notification from the Stackable * * @param int $timeout An optional timeout in microseconds * * @link http://www.php.net/manual/en/threaded.wait.php * @return bool A boolean indication of success */ public function wait($timeout){} /** * @return int */ public function getRefCount(){} public function addRef(){} public function delRef(){} /** * @return bool */ public function isGarbage(){} } /** * Basic thread implementation * * An implementation of a Thread should extend this declaration, implementing the run method. * When the start method of that object is called, the run method code will be executed in separate Thread. * * @link http://www.php.net/manual/en/class.thread.php */ class Thread extends Threaded{ /** * Will return the identity of the Thread that created the referenced Thread * * @link http://www.php.net/manual/en/thread.getcreatorid.php * @return int A numeric identity */ public function getCreatorId(){} /** * Will return the instance of currently executing thread * * @return static */ public static function getCurrentThread(){} /** * Will return the identity of the currently executing thread * * @link http://www.php.net/manual/en/thread.getcurrentthreadid.php * @return int */ public static function getCurrentThreadId(){} /** * Will return the identity of the referenced Thread * * @link http://www.php.net/manual/en/thread.getthreadid.php * @return int */ public function getThreadId(){} /** * Tell if the referenced Thread has been joined by another context * * @link http://www.php.net/manual/en/thread.isjoined.php * @return bool A boolean indication of state */ public function isJoined(){} /** * Tell if the referenced Thread has been started * * @link http://www.php.net/manual/en/thread.isstarted.php * @return bool A boolean indication of state */ public function isStarted(){} /** * Causes the calling context to wait for the referenced Thread to finish executing * * @link http://www.php.net/manual/en/thread.join.php * @return bool A boolean indication of state */ public function join(){} /** * Will start a new Thread to execute the implemented run method * * @param int $options An optional mask of inheritance constants, by default PTHREADS_INHERIT_ALL * * @link http://www.php.net/manual/en/thread.start.php * @return bool A boolean indication of success */ public function start(int $options = PTHREADS_INHERIT_ALL){} } /** * Worker * * Worker Threads have a persistent context, as such should be used over Threads in most cases. * * When a Worker is started, the run method will be executed, but the Thread will not leave until one * of the following conditions are met: * - the Worker goes out of scope (no more references remain) * - the programmer calls shutdown * - the script dies * This means the programmer can reuse the context throughout execution; placing objects on the stack of * the Worker will cause the Worker to execute the stacked objects run method. * * @link http://www.php.net/manual/en/class.worker.php */ class Worker extends Thread{ /** * Returns the number of threaded tasks waiting to be executed by the referenced Worker * * @link http://www.php.net/manual/en/worker.getstacked.php * @return int An integral value */ public function getStacked(){} /** * Tell if the referenced Worker has been shutdown * * @link http://www.php.net/manual/en/worker.isshutdown.php * @return bool A boolean indication of state */ public function isShutdown(){} public function collector(Collectable $collectable){} /** * Shuts down the Worker after executing all the threaded tasks previously stacked * * @link http://www.php.net/manual/en/worker.shutdown.php * @return bool A boolean indication of success */ public function shutdown(){} /** * Appends the referenced object to the stack of the referenced Worker * * @param Collectable $work Collectable object to be executed by the referenced Worker * * @link http://www.php.net/manual/en/worker.stack.php * @return int The new length of the stack */ public function stack(Collectable &$work){} /** * Removes the first item from the stack * * @link http://www.php.net/manual/en/worker.unstack.php * @return int The new length of the stack */ public function unstack(){} /** * Collects finished objects * * @param callable $function * * @link http://www.php.net/manual/en/worker.collect.php * @return void */ public function collect(callable $function){} } /** * Pool class * * A Pool is a container for, and controller of, a number of Worker threads, the number of threads can be adjusted * during execution, additionally the Pool provides an easy mechanism to maintain and collect references in the * proper way. * * @link http://www.php.net/manual/en/class.pool.php */ class Pool{ /** * The maximum number of Worker threads allowed in this Pool * * @var integer */ protected $size; /** * The name of the Worker class for this Pool * * @var string */ protected $class; /** * The array of Worker threads for this Pool * * @var array|Worker[] */ protected $workers; /** * The constructor arguments to be passed by this Pool to new Workers upon construction * * @var array */ protected $ctor; /** * The numeric identifier for the last Worker used by this Pool * * @var integer */ protected $last; /** * Construct a new Pool of Workers * * @param integer $size The maximum number of Workers this Pool can create * @param string|null $class The class for new Workers * @param array|null $ctor An array of arguments to be passed to new Workers * * @link http://www.php.net/manual/en/pool.__construct.php */ public function __construct($size, $class, $ctor = []){} /** * Collect references to completed tasks * * Allows the Pool to collect references determined to be garbage by the given collector * * @param callable $collector * * @link http://www.php.net/manual/en/pool.collect.php */ public function collect(callable $collector){} /** * Resize the Pool * * @param integer $size The maximum number of Workers this Pool can create * * @link http://www.php.net/manual/en/pool.resize.php */ public function resize($size){} /** * Shutdown all Workers in this Pool * * @link http://www.php.net/manual/en/pool.shutdown.php */ public function shutdown(){} /** * Submit the task to the next Worker in the Pool * * @param Threaded $task The task for execution * * @return int the identifier of the Worker executing the object */ public function submit(Threaded $task){} /** * Submit the task to the specific Worker in the Pool * * @param int $worker The worker for execution * @param Threaded $task The task for execution * * @return int the identifier of the Worker that accepted the object */ public function submitTo($worker, Threaded $task){} }